@memberjunction/server 5.38.0 → 5.40.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/README.md +67 -0
- package/dist/apolloServer/index.d.ts +0 -8
- package/dist/apolloServer/index.d.ts.map +1 -1
- package/dist/apolloServer/index.js +61 -0
- package/dist/apolloServer/index.js.map +1 -1
- package/dist/auth/index.js +1 -1
- package/dist/auth/index.js.map +1 -1
- package/dist/auth/magicLink/MagicLinkKeys.d.ts +58 -0
- package/dist/auth/magicLink/MagicLinkKeys.d.ts.map +1 -0
- package/dist/auth/magicLink/MagicLinkKeys.js +99 -0
- package/dist/auth/magicLink/MagicLinkKeys.js.map +1 -0
- package/dist/auth/magicLink/MagicLinkRouter.d.ts +33 -0
- package/dist/auth/magicLink/MagicLinkRouter.d.ts.map +1 -0
- package/dist/auth/magicLink/MagicLinkRouter.js +184 -0
- package/dist/auth/magicLink/MagicLinkRouter.js.map +1 -0
- package/dist/auth/magicLink/MagicLinkService.d.ts +123 -0
- package/dist/auth/magicLink/MagicLinkService.d.ts.map +1 -0
- package/dist/auth/magicLink/MagicLinkService.js +605 -0
- package/dist/auth/magicLink/MagicLinkService.js.map +1 -0
- package/dist/auth/magicLink/index.d.ts +6 -0
- package/dist/auth/magicLink/index.d.ts.map +1 -0
- package/dist/auth/magicLink/index.js +6 -0
- package/dist/auth/magicLink/index.js.map +1 -0
- package/dist/auth/magicLink/magicLinkCore.d.ts +82 -0
- package/dist/auth/magicLink/magicLinkCore.d.ts.map +1 -0
- package/dist/auth/magicLink/magicLinkCore.js +164 -0
- package/dist/auth/magicLink/magicLinkCore.js.map +1 -0
- package/dist/auth/magicLink/redeemLanding.d.ts +22 -0
- package/dist/auth/magicLink/redeemLanding.d.ts.map +1 -0
- package/dist/auth/magicLink/redeemLanding.js +61 -0
- package/dist/auth/magicLink/redeemLanding.js.map +1 -0
- package/dist/auth/magicLink/types.d.ts +131 -0
- package/dist/auth/magicLink/types.d.ts.map +1 -0
- package/dist/auth/magicLink/types.js +6 -0
- package/dist/auth/magicLink/types.js.map +1 -0
- package/dist/config.d.ts +479 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +84 -0
- package/dist/config.js.map +1 -1
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +223 -19
- package/dist/context.js.map +1 -1
- package/dist/generated/generated.d.ts +952 -4
- package/dist/generated/generated.d.ts.map +1 -1
- package/dist/generated/generated.js +25663 -20321
- package/dist/generated/generated.js.map +1 -1
- package/dist/generic/FireAndForgetHeartbeat.d.ts +51 -0
- package/dist/generic/FireAndForgetHeartbeat.d.ts.map +1 -0
- package/dist/generic/FireAndForgetHeartbeat.js +44 -0
- package/dist/generic/FireAndForgetHeartbeat.js.map +1 -0
- package/dist/generic/ResolverBase.d.ts.map +1 -1
- package/dist/generic/ResolverBase.js +35 -7
- package/dist/generic/ResolverBase.js.map +1 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +145 -2
- package/dist/index.js.map +1 -1
- package/dist/logging/NoLog.d.ts +50 -0
- package/dist/logging/NoLog.d.ts.map +1 -0
- package/dist/logging/NoLog.js +80 -0
- package/dist/logging/NoLog.js.map +1 -0
- package/dist/logging/bootAudit.d.ts +43 -0
- package/dist/logging/bootAudit.d.ts.map +1 -0
- package/dist/logging/bootAudit.js +83 -0
- package/dist/logging/bootAudit.js.map +1 -0
- package/dist/logging/boundaryLogPayload.d.ts +18 -0
- package/dist/logging/boundaryLogPayload.d.ts.map +1 -0
- package/dist/logging/boundaryLogPayload.js +18 -0
- package/dist/logging/boundaryLogPayload.js.map +1 -0
- package/dist/logging/secretRedactor.d.ts +23 -0
- package/dist/logging/secretRedactor.d.ts.map +1 -0
- package/dist/logging/secretRedactor.js +53 -0
- package/dist/logging/secretRedactor.js.map +1 -0
- package/dist/logging/shortenForLog.d.ts +8 -0
- package/dist/logging/shortenForLog.d.ts.map +1 -0
- package/dist/logging/shortenForLog.js +21 -0
- package/dist/logging/shortenForLog.js.map +1 -0
- package/dist/logging/variablesLoggingMiddleware.d.ts +22 -0
- package/dist/logging/variablesLoggingMiddleware.d.ts.map +1 -0
- package/dist/logging/variablesLoggingMiddleware.js +127 -0
- package/dist/logging/variablesLoggingMiddleware.js.map +1 -0
- package/dist/resolvers/CurrentUserContextResolver.d.ts +9 -3
- package/dist/resolvers/CurrentUserContextResolver.d.ts.map +1 -1
- package/dist/resolvers/CurrentUserContextResolver.js +19 -5
- package/dist/resolvers/CurrentUserContextResolver.js.map +1 -1
- package/dist/resolvers/EntityResolver.d.ts.map +1 -1
- package/dist/resolvers/EntityResolver.js +13 -2
- package/dist/resolvers/EntityResolver.js.map +1 -1
- package/dist/resolvers/GenerateSeedTaxonomyResolver.d.ts +28 -0
- package/dist/resolvers/GenerateSeedTaxonomyResolver.d.ts.map +1 -0
- package/dist/resolvers/GenerateSeedTaxonomyResolver.js +100 -0
- package/dist/resolvers/GenerateSeedTaxonomyResolver.js.map +1 -0
- package/dist/resolvers/GetDataResolver.d.ts.map +1 -1
- package/dist/resolvers/GetDataResolver.js +8 -4
- package/dist/resolvers/GetDataResolver.js.map +1 -1
- package/dist/resolvers/IntegrationDiscoveryResolver.d.ts +259 -2
- package/dist/resolvers/IntegrationDiscoveryResolver.d.ts.map +1 -1
- package/dist/resolvers/IntegrationDiscoveryResolver.js +1276 -117
- package/dist/resolvers/IntegrationDiscoveryResolver.js.map +1 -1
- package/dist/resolvers/IntegrationProgressResolver.d.ts +90 -0
- package/dist/resolvers/IntegrationProgressResolver.d.ts.map +1 -0
- package/dist/resolvers/IntegrationProgressResolver.js +196 -0
- package/dist/resolvers/IntegrationProgressResolver.js.map +1 -0
- package/dist/resolvers/MCPResolver.d.ts.map +1 -1
- package/dist/resolvers/MCPResolver.js +19 -14
- package/dist/resolvers/MCPResolver.js.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.js +26 -5
- package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
- package/dist/resolvers/RunClusterAnalysisResolver.d.ts +74 -0
- package/dist/resolvers/RunClusterAnalysisResolver.d.ts.map +1 -0
- package/dist/resolvers/RunClusterAnalysisResolver.js +243 -0
- package/dist/resolvers/RunClusterAnalysisResolver.js.map +1 -0
- package/dist/resolvers/RunTestResolver.d.ts.map +1 -1
- package/dist/resolvers/RunTestResolver.js +12 -2
- package/dist/resolvers/RunTestResolver.js.map +1 -1
- package/dist/resolvers/SearchEntitiesResolver.d.ts +46 -0
- package/dist/resolvers/SearchEntitiesResolver.d.ts.map +1 -0
- package/dist/resolvers/SearchEntitiesResolver.js +216 -0
- package/dist/resolvers/SearchEntitiesResolver.js.map +1 -0
- package/dist/resolvers/UserResolver.d.ts +16 -2
- package/dist/resolvers/UserResolver.d.ts.map +1 -1
- package/dist/resolvers/UserResolver.js +45 -2
- package/dist/resolvers/UserResolver.js.map +1 -1
- package/dist/resolvers/VectorizeEntityResolver.d.ts.map +1 -1
- package/dist/resolvers/VectorizeEntityResolver.js +14 -1
- package/dist/resolvers/VectorizeEntityResolver.js.map +1 -1
- package/dist/rest/SignatureWebhookHandler.d.ts +19 -0
- package/dist/rest/SignatureWebhookHandler.d.ts.map +1 -0
- package/dist/rest/SignatureWebhookHandler.js +86 -0
- package/dist/rest/SignatureWebhookHandler.js.map +1 -0
- package/dist/services/ScheduledJobsService.d.ts.map +1 -1
- package/dist/services/ScheduledJobsService.js +14 -2
- package/dist/services/ScheduledJobsService.js.map +1 -1
- package/package.json +79 -74
- package/src/__tests__/NoLog.test.ts +76 -0
- package/src/__tests__/bootAudit.test.ts +188 -0
- package/src/__tests__/boundaryLogPayload.test.ts +31 -0
- package/src/__tests__/getDataTokenRedaction.test.ts +84 -0
- package/src/__tests__/magicLink.test.ts +387 -0
- package/src/__tests__/secretRedactor.test.ts +163 -0
- package/src/__tests__/subscriptionRedaction.test.ts +217 -0
- package/src/apolloServer/index.ts +58 -0
- package/src/auth/index.ts +2 -2
- package/src/auth/magicLink/MagicLinkKeys.ts +122 -0
- package/src/auth/magicLink/MagicLinkRouter.ts +209 -0
- package/src/auth/magicLink/MagicLinkService.ts +724 -0
- package/src/auth/magicLink/index.ts +17 -0
- package/src/auth/magicLink/magicLinkCore.ts +216 -0
- package/src/auth/magicLink/redeemLanding.ts +62 -0
- package/src/auth/magicLink/types.ts +137 -0
- package/src/config.ts +89 -0
- package/src/context.ts +249 -17
- package/src/generated/generated.ts +12528 -8866
- package/src/generic/FireAndForgetHeartbeat.ts +85 -0
- package/src/generic/ResolverBase.ts +35 -7
- package/src/generic/__tests__/FireAndForgetHeartbeat.test.ts +99 -0
- package/src/index.ts +165 -2
- package/src/logging/NoLog.ts +88 -0
- package/src/logging/bootAudit.ts +117 -0
- package/src/logging/boundaryLogPayload.ts +19 -0
- package/src/logging/secretRedactor.ts +82 -0
- package/src/logging/shortenForLog.ts +17 -0
- package/src/logging/variablesLoggingMiddleware.ts +191 -0
- package/src/resolvers/CurrentUserContextResolver.ts +21 -5
- package/src/resolvers/EntityResolver.ts +17 -5
- package/src/resolvers/GenerateSeedTaxonomyResolver.ts +90 -0
- package/src/resolvers/GetDataResolver.ts +9 -5
- package/src/resolvers/IntegrationDiscoveryResolver.ts +1111 -120
- package/src/resolvers/IntegrationProgressResolver.ts +220 -0
- package/src/resolvers/MCPResolver.ts +18 -14
- package/src/resolvers/RunAIAgentResolver.ts +28 -5
- package/src/resolvers/RunClusterAnalysisResolver.ts +249 -0
- package/src/resolvers/RunTestResolver.ts +14 -2
- package/src/resolvers/SearchEntitiesResolver.ts +173 -0
- package/src/resolvers/UserResolver.ts +38 -2
- package/src/resolvers/VectorizeEntityResolver.ts +14 -1
- package/src/resolvers/__tests__/IntegrationProgressResolver.test.ts +170 -0
- package/src/rest/SignatureWebhookHandler.ts +103 -0
- package/src/services/ScheduledJobsService.ts +15 -2
|
@@ -12,13 +12,14 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
|
12
12
|
};
|
|
13
13
|
var IntegrationDiscoveryResolver_1;
|
|
14
14
|
import { Resolver, Query, Mutation, Arg, Ctx, ObjectType, Field, InputType } from "type-graphql";
|
|
15
|
-
import { CompositeKey, LocalCacheManager, RunView, LogError } from "@memberjunction/core";
|
|
15
|
+
import { CompositeKey, LocalCacheManager, Metadata, RunView, LogError, LogStatus } from "@memberjunction/core";
|
|
16
16
|
import { GetReadOnlyProvider, GetReadWriteProvider } from "../util.js";
|
|
17
17
|
import { CronExpressionHelper } from "@memberjunction/scheduling-engine";
|
|
18
|
-
import { ConnectorFactory, IntegrationEngine, IntegrationSchemaSync } from "@memberjunction/integration-engine";
|
|
18
|
+
import { ConnectorFactory, IntegrationEngine, IntegrationSchemaSync, IntegrationConnectorCreationPipeline, IntegrationActionGenerator } from "@memberjunction/integration-engine";
|
|
19
19
|
import { IntegrationEngineBase } from "@memberjunction/integration-engine-base";
|
|
20
20
|
import { SchemaBuilder, TypeMapper, SchemaEvolution } from "@memberjunction/integration-schema-builder";
|
|
21
21
|
import { RuntimeSchemaManager } from "@memberjunction/schema-engine";
|
|
22
|
+
import { IntegrationProgressReader } from "@memberjunction/integration-progress-artifacts";
|
|
22
23
|
import { ResolverBase } from "../generic/ResolverBase.js";
|
|
23
24
|
import { UserCache } from "@memberjunction/sqlserver-dataprovider";
|
|
24
25
|
// ─── RSU Pipeline Output Types ──────────────────────────────────────────────
|
|
@@ -412,6 +413,10 @@ __decorate([
|
|
|
412
413
|
Field({ nullable: true }),
|
|
413
414
|
__metadata("design:type", Number)
|
|
414
415
|
], DeleteConnectionOutput.prototype, "SchedulesDeleted", void 0);
|
|
416
|
+
__decorate([
|
|
417
|
+
Field({ nullable: true }),
|
|
418
|
+
__metadata("design:type", Boolean)
|
|
419
|
+
], DeleteConnectionOutput.prototype, "CredentialDeleted", void 0);
|
|
415
420
|
DeleteConnectionOutput = __decorate([
|
|
416
421
|
ObjectType()
|
|
417
422
|
], DeleteConnectionOutput);
|
|
@@ -683,6 +688,151 @@ __decorate([
|
|
|
683
688
|
ConnectionTestOutput = __decorate([
|
|
684
689
|
ObjectType()
|
|
685
690
|
], ConnectionTestOutput);
|
|
691
|
+
// ─── Refresh Connector Schema (Phase 0 v5.39.x) ─────────────────────────────
|
|
692
|
+
// Invokes IntegrationConnectorCreationPipeline.Run() which drives
|
|
693
|
+
// TestConnection → IntrospectSchema (parallel describe) →
|
|
694
|
+
// PersistDiscoveredSchema (overlay precedence: declared wins for semantic,
|
|
695
|
+
// discovered wins for technical) → SoftPKClassifier (4-tier cascade) and emits
|
|
696
|
+
// structured progress events the operator can grep in the MJAPI log file:
|
|
697
|
+
// "event":"discovery.object.added" with source: Declared | Discovered | Custom
|
|
698
|
+
// "event":"discovery.field.added" ditto
|
|
699
|
+
// "event":"pk.classifier.invoked" per object missing an explicit PK
|
|
700
|
+
// "event":"pk.classifier.result" the classifier's verdict + strategy + reason
|
|
701
|
+
// "event":"entity.generated" IO has a PK → eligible for MJ entity generation
|
|
702
|
+
// "event":"entity.skipped-no-pk" IO has NO PK → not eligible, deferred
|
|
703
|
+
//
|
|
704
|
+
// Per-run JSONL artifacts also land at:
|
|
705
|
+
// <cwd>/logs/integration-runs/<runID>/{manifest,progress,result}.json
|
|
706
|
+
let RefreshConnectorSchemaPKVerdictOutput = class RefreshConnectorSchemaPKVerdictOutput {
|
|
707
|
+
};
|
|
708
|
+
__decorate([
|
|
709
|
+
Field(),
|
|
710
|
+
__metadata("design:type", String)
|
|
711
|
+
], RefreshConnectorSchemaPKVerdictOutput.prototype, "ObjectName", void 0);
|
|
712
|
+
__decorate([
|
|
713
|
+
Field(),
|
|
714
|
+
__metadata("design:type", Boolean)
|
|
715
|
+
], RefreshConnectorSchemaPKVerdictOutput.prototype, "Confident", void 0);
|
|
716
|
+
__decorate([
|
|
717
|
+
Field({ nullable: true }),
|
|
718
|
+
__metadata("design:type", String)
|
|
719
|
+
], RefreshConnectorSchemaPKVerdictOutput.prototype, "Nominee", void 0);
|
|
720
|
+
__decorate([
|
|
721
|
+
Field(),
|
|
722
|
+
__metadata("design:type", Number)
|
|
723
|
+
], RefreshConnectorSchemaPKVerdictOutput.prototype, "Confidence", void 0);
|
|
724
|
+
__decorate([
|
|
725
|
+
Field(),
|
|
726
|
+
__metadata("design:type", String)
|
|
727
|
+
], RefreshConnectorSchemaPKVerdictOutput.prototype, "Strategy", void 0);
|
|
728
|
+
__decorate([
|
|
729
|
+
Field(),
|
|
730
|
+
__metadata("design:type", String)
|
|
731
|
+
], RefreshConnectorSchemaPKVerdictOutput.prototype, "Reason", void 0);
|
|
732
|
+
RefreshConnectorSchemaPKVerdictOutput = __decorate([
|
|
733
|
+
ObjectType()
|
|
734
|
+
], RefreshConnectorSchemaPKVerdictOutput);
|
|
735
|
+
let RefreshConnectorSchemaOutput = class RefreshConnectorSchemaOutput {
|
|
736
|
+
};
|
|
737
|
+
__decorate([
|
|
738
|
+
Field(),
|
|
739
|
+
__metadata("design:type", Boolean)
|
|
740
|
+
], RefreshConnectorSchemaOutput.prototype, "Success", void 0);
|
|
741
|
+
__decorate([
|
|
742
|
+
Field(),
|
|
743
|
+
__metadata("design:type", String)
|
|
744
|
+
], RefreshConnectorSchemaOutput.prototype, "Message", void 0);
|
|
745
|
+
__decorate([
|
|
746
|
+
Field(),
|
|
747
|
+
__metadata("design:type", String)
|
|
748
|
+
], RefreshConnectorSchemaOutput.prototype, "RunID", void 0);
|
|
749
|
+
__decorate([
|
|
750
|
+
Field({ nullable: true }),
|
|
751
|
+
__metadata("design:type", Number)
|
|
752
|
+
], RefreshConnectorSchemaOutput.prototype, "ObjectsCreated", void 0);
|
|
753
|
+
__decorate([
|
|
754
|
+
Field({ nullable: true }),
|
|
755
|
+
__metadata("design:type", Number)
|
|
756
|
+
], RefreshConnectorSchemaOutput.prototype, "ObjectsUpdated", void 0);
|
|
757
|
+
__decorate([
|
|
758
|
+
Field({ nullable: true }),
|
|
759
|
+
__metadata("design:type", Number)
|
|
760
|
+
], RefreshConnectorSchemaOutput.prototype, "FieldsCreated", void 0);
|
|
761
|
+
__decorate([
|
|
762
|
+
Field({ nullable: true }),
|
|
763
|
+
__metadata("design:type", Number)
|
|
764
|
+
], RefreshConnectorSchemaOutput.prototype, "FieldsUpdated", void 0);
|
|
765
|
+
__decorate([
|
|
766
|
+
Field(() => [RefreshConnectorSchemaPKVerdictOutput], { nullable: true }),
|
|
767
|
+
__metadata("design:type", Array)
|
|
768
|
+
], RefreshConnectorSchemaOutput.prototype, "PKVerdicts", void 0);
|
|
769
|
+
__decorate([
|
|
770
|
+
Field(() => [String], { nullable: true }),
|
|
771
|
+
__metadata("design:type", Array)
|
|
772
|
+
], RefreshConnectorSchemaOutput.prototype, "UnresolvedObjects", void 0);
|
|
773
|
+
__decorate([
|
|
774
|
+
Field({ nullable: true }),
|
|
775
|
+
__metadata("design:type", String)
|
|
776
|
+
], RefreshConnectorSchemaOutput.prototype, "FailureMessage", void 0);
|
|
777
|
+
RefreshConnectorSchemaOutput = __decorate([
|
|
778
|
+
ObjectType()
|
|
779
|
+
], RefreshConnectorSchemaOutput);
|
|
780
|
+
// ─── Generate Integration Action (on-demand Integration-as-Actions) ─────────
|
|
781
|
+
// Generates + persists a strongly-typed Action (DriverClass='IntegrationActionExecutor')
|
|
782
|
+
// for one integration/object/verb (or all applicable verbs when verb is omitted) via
|
|
783
|
+
// the engine's IntegrationActionGenerator. Idempotent on the deterministic Action Name
|
|
784
|
+
// "<Integration> - <Verb> <DisplayName>": a matching Action is reused (AlreadyExisted=true)
|
|
785
|
+
// and its params/result codes reconciled rather than duplicated.
|
|
786
|
+
let IntegrationGenerateActionResult = class IntegrationGenerateActionResult {
|
|
787
|
+
};
|
|
788
|
+
__decorate([
|
|
789
|
+
Field(),
|
|
790
|
+
__metadata("design:type", Boolean)
|
|
791
|
+
], IntegrationGenerateActionResult.prototype, "Success", void 0);
|
|
792
|
+
__decorate([
|
|
793
|
+
Field({ nullable: true }),
|
|
794
|
+
__metadata("design:type", String)
|
|
795
|
+
], IntegrationGenerateActionResult.prototype, "ActionID", void 0);
|
|
796
|
+
__decorate([
|
|
797
|
+
Field({ nullable: true }),
|
|
798
|
+
__metadata("design:type", String)
|
|
799
|
+
], IntegrationGenerateActionResult.prototype, "ActionName", void 0);
|
|
800
|
+
__decorate([
|
|
801
|
+
Field(),
|
|
802
|
+
__metadata("design:type", Boolean)
|
|
803
|
+
], IntegrationGenerateActionResult.prototype, "AlreadyExisted", void 0);
|
|
804
|
+
__decorate([
|
|
805
|
+
Field({ nullable: true }),
|
|
806
|
+
__metadata("design:type", String)
|
|
807
|
+
], IntegrationGenerateActionResult.prototype, "Verb", void 0);
|
|
808
|
+
__decorate([
|
|
809
|
+
Field({ nullable: true }),
|
|
810
|
+
__metadata("design:type", String)
|
|
811
|
+
], IntegrationGenerateActionResult.prototype, "ObjectName", void 0);
|
|
812
|
+
__decorate([
|
|
813
|
+
Field(),
|
|
814
|
+
__metadata("design:type", String)
|
|
815
|
+
], IntegrationGenerateActionResult.prototype, "Message", void 0);
|
|
816
|
+
IntegrationGenerateActionResult = __decorate([
|
|
817
|
+
ObjectType()
|
|
818
|
+
], IntegrationGenerateActionResult);
|
|
819
|
+
let IntegrationGenerateActionOutput = class IntegrationGenerateActionOutput {
|
|
820
|
+
};
|
|
821
|
+
__decorate([
|
|
822
|
+
Field(),
|
|
823
|
+
__metadata("design:type", Boolean)
|
|
824
|
+
], IntegrationGenerateActionOutput.prototype, "Success", void 0);
|
|
825
|
+
__decorate([
|
|
826
|
+
Field(),
|
|
827
|
+
__metadata("design:type", String)
|
|
828
|
+
], IntegrationGenerateActionOutput.prototype, "Message", void 0);
|
|
829
|
+
__decorate([
|
|
830
|
+
Field(() => [IntegrationGenerateActionResult], { nullable: true }),
|
|
831
|
+
__metadata("design:type", Array)
|
|
832
|
+
], IntegrationGenerateActionOutput.prototype, "Results", void 0);
|
|
833
|
+
IntegrationGenerateActionOutput = __decorate([
|
|
834
|
+
ObjectType()
|
|
835
|
+
], IntegrationGenerateActionOutput);
|
|
686
836
|
// --- Preview Data Types ---
|
|
687
837
|
let PreviewRecordOutput = class PreviewRecordOutput {
|
|
688
838
|
};
|
|
@@ -876,6 +1026,39 @@ __decorate([
|
|
|
876
1026
|
CreateConnectionInput = __decorate([
|
|
877
1027
|
InputType()
|
|
878
1028
|
], CreateConnectionInput);
|
|
1029
|
+
let CreateConnectionPipelineSummary = class CreateConnectionPipelineSummary {
|
|
1030
|
+
};
|
|
1031
|
+
__decorate([
|
|
1032
|
+
Field(),
|
|
1033
|
+
__metadata("design:type", String)
|
|
1034
|
+
], CreateConnectionPipelineSummary.prototype, "RunID", void 0);
|
|
1035
|
+
__decorate([
|
|
1036
|
+
Field(),
|
|
1037
|
+
__metadata("design:type", Number)
|
|
1038
|
+
], CreateConnectionPipelineSummary.prototype, "ObjectsCreated", void 0);
|
|
1039
|
+
__decorate([
|
|
1040
|
+
Field(),
|
|
1041
|
+
__metadata("design:type", Number)
|
|
1042
|
+
], CreateConnectionPipelineSummary.prototype, "ObjectsUpdated", void 0);
|
|
1043
|
+
__decorate([
|
|
1044
|
+
Field(),
|
|
1045
|
+
__metadata("design:type", Number)
|
|
1046
|
+
], CreateConnectionPipelineSummary.prototype, "FieldsCreated", void 0);
|
|
1047
|
+
__decorate([
|
|
1048
|
+
Field(),
|
|
1049
|
+
__metadata("design:type", Number)
|
|
1050
|
+
], CreateConnectionPipelineSummary.prototype, "FieldsUpdated", void 0);
|
|
1051
|
+
__decorate([
|
|
1052
|
+
Field(() => [String]),
|
|
1053
|
+
__metadata("design:type", Array)
|
|
1054
|
+
], CreateConnectionPipelineSummary.prototype, "UnresolvedObjects", void 0);
|
|
1055
|
+
__decorate([
|
|
1056
|
+
Field(() => [RefreshConnectorSchemaPKVerdictOutput], { nullable: true }),
|
|
1057
|
+
__metadata("design:type", Array)
|
|
1058
|
+
], CreateConnectionPipelineSummary.prototype, "PKVerdicts", void 0);
|
|
1059
|
+
CreateConnectionPipelineSummary = __decorate([
|
|
1060
|
+
ObjectType()
|
|
1061
|
+
], CreateConnectionPipelineSummary);
|
|
879
1062
|
let CreateConnectionOutput = class CreateConnectionOutput {
|
|
880
1063
|
};
|
|
881
1064
|
__decorate([
|
|
@@ -902,6 +1085,10 @@ __decorate([
|
|
|
902
1085
|
Field({ nullable: true }),
|
|
903
1086
|
__metadata("design:type", String)
|
|
904
1087
|
], CreateConnectionOutput.prototype, "ConnectionTestMessage", void 0);
|
|
1088
|
+
__decorate([
|
|
1089
|
+
Field(() => CreateConnectionPipelineSummary, { nullable: true }),
|
|
1090
|
+
__metadata("design:type", CreateConnectionPipelineSummary)
|
|
1091
|
+
], CreateConnectionOutput.prototype, "SchemaRefresh", void 0);
|
|
905
1092
|
CreateConnectionOutput = __decorate([
|
|
906
1093
|
ObjectType()
|
|
907
1094
|
], CreateConnectionOutput);
|
|
@@ -961,6 +1148,10 @@ __decorate([
|
|
|
961
1148
|
Field({ nullable: true, defaultValue: 0 }),
|
|
962
1149
|
__metadata("design:type", Number)
|
|
963
1150
|
], EntityMapInput.prototype, "Priority", void 0);
|
|
1151
|
+
__decorate([
|
|
1152
|
+
Field({ nullable: true }),
|
|
1153
|
+
__metadata("design:type", String)
|
|
1154
|
+
], EntityMapInput.prototype, "Configuration", void 0);
|
|
964
1155
|
__decorate([
|
|
965
1156
|
Field(() => [FieldMapInput], { nullable: true }),
|
|
966
1157
|
__metadata("design:type", Array)
|
|
@@ -1170,6 +1361,10 @@ __decorate([
|
|
|
1170
1361
|
Field({ nullable: true }),
|
|
1171
1362
|
__metadata("design:type", String)
|
|
1172
1363
|
], EntityMapUpdateInput.prototype, "Status", void 0);
|
|
1364
|
+
__decorate([
|
|
1365
|
+
Field({ nullable: true }),
|
|
1366
|
+
__metadata("design:type", String)
|
|
1367
|
+
], EntityMapUpdateInput.prototype, "Configuration", void 0);
|
|
1173
1368
|
EntityMapUpdateInput = __decorate([
|
|
1174
1369
|
InputType()
|
|
1175
1370
|
], EntityMapUpdateInput);
|
|
@@ -1203,6 +1398,10 @@ __decorate([
|
|
|
1203
1398
|
Field({ nullable: true }),
|
|
1204
1399
|
__metadata("design:type", String)
|
|
1205
1400
|
], EntityMapSummaryOutput.prototype, "Status", void 0);
|
|
1401
|
+
__decorate([
|
|
1402
|
+
Field({ nullable: true }),
|
|
1403
|
+
__metadata("design:type", String)
|
|
1404
|
+
], EntityMapSummaryOutput.prototype, "Configuration", void 0);
|
|
1206
1405
|
EntityMapSummaryOutput = __decorate([
|
|
1207
1406
|
ObjectType()
|
|
1208
1407
|
], EntityMapSummaryOutput);
|
|
@@ -1432,6 +1631,218 @@ __decorate([
|
|
|
1432
1631
|
OperationProgressOutput = __decorate([
|
|
1433
1632
|
ObjectType()
|
|
1434
1633
|
], OperationProgressOutput);
|
|
1634
|
+
// ── STRUCTURED RUN ARTIFACTS (durable JSONL progress streams) ─────────
|
|
1635
|
+
// These expose the IntegrationProgressReader over GraphQL so a tenant can ask,
|
|
1636
|
+
// at any time, "what exactly happened (or is happening) on this run?" — backed
|
|
1637
|
+
// by the append-only <cwd>/logs/integration-runs/<runID>/progress.jsonl files
|
|
1638
|
+
// that survive an MJAPI restart and grow as the run progresses. Poll
|
|
1639
|
+
// IntegrationTailRunEvents(runID, sinceSeq) to follow a live run incrementally.
|
|
1640
|
+
let IntegrationRunCountsOutput = class IntegrationRunCountsOutput {
|
|
1641
|
+
};
|
|
1642
|
+
__decorate([
|
|
1643
|
+
Field({ nullable: true }),
|
|
1644
|
+
__metadata("design:type", Number)
|
|
1645
|
+
], IntegrationRunCountsOutput.prototype, "Processed", void 0);
|
|
1646
|
+
__decorate([
|
|
1647
|
+
Field({ nullable: true }),
|
|
1648
|
+
__metadata("design:type", Number)
|
|
1649
|
+
], IntegrationRunCountsOutput.prototype, "Succeeded", void 0);
|
|
1650
|
+
__decorate([
|
|
1651
|
+
Field({ nullable: true }),
|
|
1652
|
+
__metadata("design:type", Number)
|
|
1653
|
+
], IntegrationRunCountsOutput.prototype, "Failed", void 0);
|
|
1654
|
+
__decorate([
|
|
1655
|
+
Field({ nullable: true }),
|
|
1656
|
+
__metadata("design:type", Number)
|
|
1657
|
+
], IntegrationRunCountsOutput.prototype, "Skipped", void 0);
|
|
1658
|
+
__decorate([
|
|
1659
|
+
Field({ nullable: true }),
|
|
1660
|
+
__metadata("design:type", Number)
|
|
1661
|
+
], IntegrationRunCountsOutput.prototype, "TotalKnown", void 0);
|
|
1662
|
+
IntegrationRunCountsOutput = __decorate([
|
|
1663
|
+
ObjectType()
|
|
1664
|
+
], IntegrationRunCountsOutput);
|
|
1665
|
+
let IntegrationRunSummaryArtifactOutput = class IntegrationRunSummaryArtifactOutput {
|
|
1666
|
+
};
|
|
1667
|
+
__decorate([
|
|
1668
|
+
Field(),
|
|
1669
|
+
__metadata("design:type", String)
|
|
1670
|
+
], IntegrationRunSummaryArtifactOutput.prototype, "RunID", void 0);
|
|
1671
|
+
__decorate([
|
|
1672
|
+
Field(),
|
|
1673
|
+
__metadata("design:type", String)
|
|
1674
|
+
], IntegrationRunSummaryArtifactOutput.prototype, "RunKind", void 0);
|
|
1675
|
+
__decorate([
|
|
1676
|
+
Field({ nullable: true }),
|
|
1677
|
+
__metadata("design:type", String)
|
|
1678
|
+
], IntegrationRunSummaryArtifactOutput.prototype, "IntegrationID", void 0);
|
|
1679
|
+
__decorate([
|
|
1680
|
+
Field({ nullable: true }),
|
|
1681
|
+
__metadata("design:type", String)
|
|
1682
|
+
], IntegrationRunSummaryArtifactOutput.prototype, "CompanyIntegrationID", void 0);
|
|
1683
|
+
__decorate([
|
|
1684
|
+
Field({ nullable: true }),
|
|
1685
|
+
__metadata("design:type", String)
|
|
1686
|
+
], IntegrationRunSummaryArtifactOutput.prototype, "ObjectName", void 0);
|
|
1687
|
+
__decorate([
|
|
1688
|
+
Field({ nullable: true }),
|
|
1689
|
+
__metadata("design:type", String)
|
|
1690
|
+
], IntegrationRunSummaryArtifactOutput.prototype, "TriggerType", void 0);
|
|
1691
|
+
__decorate([
|
|
1692
|
+
Field(),
|
|
1693
|
+
__metadata("design:type", String)
|
|
1694
|
+
], IntegrationRunSummaryArtifactOutput.prototype, "StartedAt", void 0);
|
|
1695
|
+
__decorate([
|
|
1696
|
+
Field(),
|
|
1697
|
+
__metadata("design:type", Boolean)
|
|
1698
|
+
], IntegrationRunSummaryArtifactOutput.prototype, "IsInFlight", void 0);
|
|
1699
|
+
__decorate([
|
|
1700
|
+
Field(),
|
|
1701
|
+
__metadata("design:type", Number)
|
|
1702
|
+
], IntegrationRunSummaryArtifactOutput.prototype, "EventCount", void 0);
|
|
1703
|
+
__decorate([
|
|
1704
|
+
Field({ nullable: true }),
|
|
1705
|
+
__metadata("design:type", Boolean)
|
|
1706
|
+
], IntegrationRunSummaryArtifactOutput.prototype, "Success", void 0);
|
|
1707
|
+
__decorate([
|
|
1708
|
+
Field({ nullable: true }),
|
|
1709
|
+
__metadata("design:type", String)
|
|
1710
|
+
], IntegrationRunSummaryArtifactOutput.prototype, "ExitReason", void 0);
|
|
1711
|
+
__decorate([
|
|
1712
|
+
Field({ nullable: true }),
|
|
1713
|
+
__metadata("design:type", String)
|
|
1714
|
+
], IntegrationRunSummaryArtifactOutput.prototype, "CompletedAt", void 0);
|
|
1715
|
+
__decorate([
|
|
1716
|
+
Field({ nullable: true }),
|
|
1717
|
+
__metadata("design:type", Number)
|
|
1718
|
+
], IntegrationRunSummaryArtifactOutput.prototype, "DurationMs", void 0);
|
|
1719
|
+
__decorate([
|
|
1720
|
+
Field({ nullable: true }),
|
|
1721
|
+
__metadata("design:type", String)
|
|
1722
|
+
], IntegrationRunSummaryArtifactOutput.prototype, "LatestEventType", void 0);
|
|
1723
|
+
__decorate([
|
|
1724
|
+
Field({ nullable: true }),
|
|
1725
|
+
__metadata("design:type", String)
|
|
1726
|
+
], IntegrationRunSummaryArtifactOutput.prototype, "LatestMessage", void 0);
|
|
1727
|
+
__decorate([
|
|
1728
|
+
Field(() => IntegrationRunCountsOutput, { nullable: true }),
|
|
1729
|
+
__metadata("design:type", IntegrationRunCountsOutput)
|
|
1730
|
+
], IntegrationRunSummaryArtifactOutput.prototype, "Counts", void 0);
|
|
1731
|
+
__decorate([
|
|
1732
|
+
Field({ nullable: true }),
|
|
1733
|
+
__metadata("design:type", Number)
|
|
1734
|
+
], IntegrationRunSummaryArtifactOutput.prototype, "WarningCount", void 0);
|
|
1735
|
+
__decorate([
|
|
1736
|
+
Field(() => [String], { nullable: true }),
|
|
1737
|
+
__metadata("design:type", Array)
|
|
1738
|
+
], IntegrationRunSummaryArtifactOutput.prototype, "Warnings", void 0);
|
|
1739
|
+
IntegrationRunSummaryArtifactOutput = __decorate([
|
|
1740
|
+
ObjectType()
|
|
1741
|
+
], IntegrationRunSummaryArtifactOutput);
|
|
1742
|
+
let IntegrationRunEventOutput = class IntegrationRunEventOutput {
|
|
1743
|
+
};
|
|
1744
|
+
__decorate([
|
|
1745
|
+
Field(),
|
|
1746
|
+
__metadata("design:type", String)
|
|
1747
|
+
], IntegrationRunEventOutput.prototype, "Ts", void 0);
|
|
1748
|
+
__decorate([
|
|
1749
|
+
Field(),
|
|
1750
|
+
__metadata("design:type", Number)
|
|
1751
|
+
], IntegrationRunEventOutput.prototype, "Seq", void 0);
|
|
1752
|
+
__decorate([
|
|
1753
|
+
Field(),
|
|
1754
|
+
__metadata("design:type", String)
|
|
1755
|
+
], IntegrationRunEventOutput.prototype, "EventType", void 0);
|
|
1756
|
+
__decorate([
|
|
1757
|
+
Field({ nullable: true }),
|
|
1758
|
+
__metadata("design:type", String)
|
|
1759
|
+
], IntegrationRunEventOutput.prototype, "Level", void 0);
|
|
1760
|
+
__decorate([
|
|
1761
|
+
Field({ nullable: true }),
|
|
1762
|
+
__metadata("design:type", String)
|
|
1763
|
+
], IntegrationRunEventOutput.prototype, "Stage", void 0);
|
|
1764
|
+
__decorate([
|
|
1765
|
+
Field({ nullable: true }),
|
|
1766
|
+
__metadata("design:type", String)
|
|
1767
|
+
], IntegrationRunEventOutput.prototype, "Message", void 0);
|
|
1768
|
+
__decorate([
|
|
1769
|
+
Field(() => IntegrationRunCountsOutput, { nullable: true }),
|
|
1770
|
+
__metadata("design:type", IntegrationRunCountsOutput)
|
|
1771
|
+
], IntegrationRunEventOutput.prototype, "Counts", void 0);
|
|
1772
|
+
__decorate([
|
|
1773
|
+
Field({ nullable: true }),
|
|
1774
|
+
__metadata("design:type", String)
|
|
1775
|
+
], IntegrationRunEventOutput.prototype, "DataJSON", void 0);
|
|
1776
|
+
__decorate([
|
|
1777
|
+
Field({ nullable: true }),
|
|
1778
|
+
__metadata("design:type", String)
|
|
1779
|
+
], IntegrationRunEventOutput.prototype, "ResumableStateJSON", void 0);
|
|
1780
|
+
IntegrationRunEventOutput = __decorate([
|
|
1781
|
+
ObjectType()
|
|
1782
|
+
], IntegrationRunEventOutput);
|
|
1783
|
+
let IntegrationListRunsOutput = class IntegrationListRunsOutput {
|
|
1784
|
+
};
|
|
1785
|
+
__decorate([
|
|
1786
|
+
Field(),
|
|
1787
|
+
__metadata("design:type", Boolean)
|
|
1788
|
+
], IntegrationListRunsOutput.prototype, "Success", void 0);
|
|
1789
|
+
__decorate([
|
|
1790
|
+
Field(),
|
|
1791
|
+
__metadata("design:type", String)
|
|
1792
|
+
], IntegrationListRunsOutput.prototype, "Message", void 0);
|
|
1793
|
+
__decorate([
|
|
1794
|
+
Field(() => [IntegrationRunSummaryArtifactOutput], { nullable: true }),
|
|
1795
|
+
__metadata("design:type", Array)
|
|
1796
|
+
], IntegrationListRunsOutput.prototype, "Runs", void 0);
|
|
1797
|
+
IntegrationListRunsOutput = __decorate([
|
|
1798
|
+
ObjectType()
|
|
1799
|
+
], IntegrationListRunsOutput);
|
|
1800
|
+
let IntegrationRunDetailOutput = class IntegrationRunDetailOutput {
|
|
1801
|
+
};
|
|
1802
|
+
__decorate([
|
|
1803
|
+
Field(),
|
|
1804
|
+
__metadata("design:type", Boolean)
|
|
1805
|
+
], IntegrationRunDetailOutput.prototype, "Success", void 0);
|
|
1806
|
+
__decorate([
|
|
1807
|
+
Field(),
|
|
1808
|
+
__metadata("design:type", String)
|
|
1809
|
+
], IntegrationRunDetailOutput.prototype, "Message", void 0);
|
|
1810
|
+
__decorate([
|
|
1811
|
+
Field(() => IntegrationRunSummaryArtifactOutput, { nullable: true }),
|
|
1812
|
+
__metadata("design:type", IntegrationRunSummaryArtifactOutput)
|
|
1813
|
+
], IntegrationRunDetailOutput.prototype, "Run", void 0);
|
|
1814
|
+
__decorate([
|
|
1815
|
+
Field(() => [String], { nullable: true }),
|
|
1816
|
+
__metadata("design:type", Array)
|
|
1817
|
+
], IntegrationRunDetailOutput.prototype, "Errors", void 0);
|
|
1818
|
+
IntegrationRunDetailOutput = __decorate([
|
|
1819
|
+
ObjectType()
|
|
1820
|
+
], IntegrationRunDetailOutput);
|
|
1821
|
+
let IntegrationRunEventsOutput = class IntegrationRunEventsOutput {
|
|
1822
|
+
};
|
|
1823
|
+
__decorate([
|
|
1824
|
+
Field(),
|
|
1825
|
+
__metadata("design:type", Boolean)
|
|
1826
|
+
], IntegrationRunEventsOutput.prototype, "Success", void 0);
|
|
1827
|
+
__decorate([
|
|
1828
|
+
Field(),
|
|
1829
|
+
__metadata("design:type", String)
|
|
1830
|
+
], IntegrationRunEventsOutput.prototype, "Message", void 0);
|
|
1831
|
+
__decorate([
|
|
1832
|
+
Field(() => [IntegrationRunEventOutput], { nullable: true }),
|
|
1833
|
+
__metadata("design:type", Array)
|
|
1834
|
+
], IntegrationRunEventsOutput.prototype, "Events", void 0);
|
|
1835
|
+
__decorate([
|
|
1836
|
+
Field(),
|
|
1837
|
+
__metadata("design:type", Number)
|
|
1838
|
+
], IntegrationRunEventsOutput.prototype, "LatestSeq", void 0);
|
|
1839
|
+
__decorate([
|
|
1840
|
+
Field(),
|
|
1841
|
+
__metadata("design:type", Boolean)
|
|
1842
|
+
], IntegrationRunEventsOutput.prototype, "IsInFlight", void 0);
|
|
1843
|
+
IntegrationRunEventsOutput = __decorate([
|
|
1844
|
+
ObjectType()
|
|
1845
|
+
], IntegrationRunEventsOutput);
|
|
1435
1846
|
// Sync progress is now tracked inside IntegrationEngine itself via IntegrationEngine.GetSyncProgress()
|
|
1436
1847
|
let ConnectionSummaryOutput = class ConnectionSummaryOutput {
|
|
1437
1848
|
};
|
|
@@ -1501,6 +1912,11 @@ const VALID_ENTITY_MAP_STATUSES = ['Active', 'Inactive'];
|
|
|
1501
1912
|
function isValidEntityMapStatus(value) {
|
|
1502
1913
|
return VALID_ENTITY_MAP_STATUSES.includes(value);
|
|
1503
1914
|
}
|
|
1915
|
+
// IntegrationActionVerb is the engine-defined union ('Get'|'Create'|'Update'|'Delete'|'Search'|'List').
|
|
1916
|
+
const VALID_INTEGRATION_ACTION_VERBS = ['Get', 'Create', 'Update', 'Delete', 'Search', 'List'];
|
|
1917
|
+
function isValidIntegrationActionVerb(value) {
|
|
1918
|
+
return VALID_INTEGRATION_ACTION_VERBS.includes(value);
|
|
1919
|
+
}
|
|
1504
1920
|
// ─── List Source Objects (Full-Catalog Picker) ──────────────────────────────
|
|
1505
1921
|
// Returns every object the source system exposes (e.g. all ~1,800 Salesforce
|
|
1506
1922
|
// sobjects), merged with any existing IntegrationObject metadata so the UI
|
|
@@ -1771,6 +2187,144 @@ let IntegrationDiscoveryResolver = class IntegrationDiscoveryResolver extends Re
|
|
|
1771
2187
|
};
|
|
1772
2188
|
}
|
|
1773
2189
|
}
|
|
2190
|
+
/**
|
|
2191
|
+
* Refreshes the connector's IntegrationObject + IntegrationObjectField
|
|
2192
|
+
* catalog by running the Phase 0 v5.39.x IntegrationConnectorCreationPipeline.
|
|
2193
|
+
* The pipeline:
|
|
2194
|
+
* 1. TestConnection — validates credentials before any heavy work.
|
|
2195
|
+
* 2. IntrospectSchema — parallel describe across all objects.
|
|
2196
|
+
* 3. PersistDiscoveredSchema — overlay-aware upsert (declared wins for
|
|
2197
|
+
* semantic, discovered wins for technical attributes), populates
|
|
2198
|
+
* MetadataSource and per-attribute AttributeWinners.
|
|
2199
|
+
* 4. PKClassify — SoftPKClassifier 4-tier cascade for any IO
|
|
2200
|
+
* that still lacks an explicit PK marker.
|
|
2201
|
+
*
|
|
2202
|
+
* Structured progress events ride the IntegrationProgressEmitter and land
|
|
2203
|
+
* both on stdout (visible in the MJAPI log file) and in a per-run
|
|
2204
|
+
* `<cwd>/logs/integration-runs/<runID>/progress.jsonl` artifact.
|
|
2205
|
+
*/
|
|
2206
|
+
async IntegrationRefreshConnectorSchema(companyIntegrationID, universalPKConvention, ctx) {
|
|
2207
|
+
try {
|
|
2208
|
+
const user = this.getAuthenticatedUser(ctx);
|
|
2209
|
+
const provider = GetReadWriteProvider(ctx.providers);
|
|
2210
|
+
const { connector, companyIntegration } = await this.resolveConnector(companyIntegrationID, user, provider);
|
|
2211
|
+
const pipeline = new IntegrationConnectorCreationPipeline();
|
|
2212
|
+
// Cast through unknown — duplicate package type declarations
|
|
2213
|
+
// between integration-engine's resolved core-entities and this
|
|
2214
|
+
// resolver's resolved core-entities (same shape, different
|
|
2215
|
+
// identities at type level).
|
|
2216
|
+
const runOpts = {
|
|
2217
|
+
Connector: connector,
|
|
2218
|
+
CompanyIntegration: companyIntegration,
|
|
2219
|
+
ContextUser: user,
|
|
2220
|
+
Provider: provider,
|
|
2221
|
+
UniversalPKConvention: universalPKConvention || undefined,
|
|
2222
|
+
ConsoleMirror: true,
|
|
2223
|
+
TriggerType: 'Manual',
|
|
2224
|
+
};
|
|
2225
|
+
const result = await pipeline.Run(runOpts);
|
|
2226
|
+
// Refresh the metadata cache so subsequent reads see the new IO/IOF
|
|
2227
|
+
// rows the pipeline just wrote. Without this the engine returns the
|
|
2228
|
+
// pre-pipeline snapshot until the next process bootstrap.
|
|
2229
|
+
const md = provider ?? new Metadata();
|
|
2230
|
+
await md.Refresh();
|
|
2231
|
+
await IntegrationEngine.Instance.Config(true, user, provider);
|
|
2232
|
+
return {
|
|
2233
|
+
Success: result.Success,
|
|
2234
|
+
Message: result.Success
|
|
2235
|
+
? `Refresh complete: ${result.PersistResult?.ObjectsCreated ?? 0} created, ${result.PersistResult?.ObjectsUpdated ?? 0} updated, ${result.UnresolvedObjects.length} IOs still PK-less (deferred to additionalSchemaInfo authoring)`
|
|
2236
|
+
: `Refresh failed: ${result.FailureMessage ?? 'unknown error'}`,
|
|
2237
|
+
RunID: result.RunID,
|
|
2238
|
+
ObjectsCreated: result.PersistResult?.ObjectsCreated,
|
|
2239
|
+
ObjectsUpdated: result.PersistResult?.ObjectsUpdated,
|
|
2240
|
+
FieldsCreated: result.PersistResult?.FieldsCreated,
|
|
2241
|
+
FieldsUpdated: result.PersistResult?.FieldsUpdated,
|
|
2242
|
+
PKVerdicts: result.PKVerdicts.map(v => ({
|
|
2243
|
+
ObjectName: v.ObjectName,
|
|
2244
|
+
Confident: v.Confident,
|
|
2245
|
+
Nominee: v.Nominee,
|
|
2246
|
+
Confidence: v.Confidence,
|
|
2247
|
+
Strategy: v.Strategy,
|
|
2248
|
+
Reason: v.Reason,
|
|
2249
|
+
})),
|
|
2250
|
+
UnresolvedObjects: result.UnresolvedObjects,
|
|
2251
|
+
FailureMessage: result.FailureMessage,
|
|
2252
|
+
};
|
|
2253
|
+
}
|
|
2254
|
+
catch (e) {
|
|
2255
|
+
LogError(`IntegrationRefreshConnectorSchema error: ${this.formatError(e)}`);
|
|
2256
|
+
return {
|
|
2257
|
+
Success: false,
|
|
2258
|
+
Message: `Error: ${this.formatError(e)}`,
|
|
2259
|
+
RunID: 'error',
|
|
2260
|
+
};
|
|
2261
|
+
}
|
|
2262
|
+
}
|
|
2263
|
+
/**
|
|
2264
|
+
* Generates + persists strongly-typed Action metadata on demand for an
|
|
2265
|
+
* integration object. When `verb` is supplied, a single Action is generated
|
|
2266
|
+
* for that (integration, object, verb); when omitted, all applicable verbs
|
|
2267
|
+
* for the object are generated (Get/Search/List always, Create/Update/Delete
|
|
2268
|
+
* only when the object supports writes).
|
|
2269
|
+
*
|
|
2270
|
+
* Idempotent: each generated Action is keyed on the deterministic Name
|
|
2271
|
+
* "<Integration> - <Verb> <DisplayName>". An existing Action with that Name is
|
|
2272
|
+
* reused (AlreadyExisted=true) and its params/result codes reconciled rather
|
|
2273
|
+
* than duplicated. Generated Actions use DriverClass='IntegrationActionExecutor'
|
|
2274
|
+
* and carry routing info ({IntegrationName, ObjectName, Verb}) in Action.Config_;
|
|
2275
|
+
* the IntegrationActionExecutor (CoreActions) is the single runtime dispatcher.
|
|
2276
|
+
*/
|
|
2277
|
+
async IntegrationGenerateAction(integrationName, objectName, verb, ctx) {
|
|
2278
|
+
try {
|
|
2279
|
+
const user = this.getAuthenticatedUser(ctx);
|
|
2280
|
+
const provider = GetReadWriteProvider(ctx.providers);
|
|
2281
|
+
const generator = new IntegrationActionGenerator();
|
|
2282
|
+
const results = await this.runActionGenerator(generator, integrationName, objectName, verb, user, provider);
|
|
2283
|
+
return this.mapGenerateActionResults(results);
|
|
2284
|
+
}
|
|
2285
|
+
catch (e) {
|
|
2286
|
+
LogError(`IntegrationGenerateAction error: ${this.formatError(e)}`);
|
|
2287
|
+
return { Success: false, Message: `Error: ${this.formatError(e)}` };
|
|
2288
|
+
}
|
|
2289
|
+
}
|
|
2290
|
+
/**
|
|
2291
|
+
* Drives the engine's IntegrationActionGenerator: one verb when `verb` is a
|
|
2292
|
+
* valid value, all applicable verbs when it is omitted. Casts user/provider
|
|
2293
|
+
* through `unknown` at the boundary — integration-engine resolves its own copy
|
|
2294
|
+
* of core/core-entities, so the types are structurally identical but nominally
|
|
2295
|
+
* distinct (same bridge pattern used by IntegrationRefreshConnectorSchema).
|
|
2296
|
+
*/
|
|
2297
|
+
async runActionGenerator(generator, integrationName, objectName, verb, user, provider) {
|
|
2298
|
+
const u = user;
|
|
2299
|
+
const p = provider;
|
|
2300
|
+
if (verb != null && verb.length > 0) {
|
|
2301
|
+
if (!isValidIntegrationActionVerb(verb)) {
|
|
2302
|
+
throw new Error(`Invalid verb "${verb}". Must be one of: ${VALID_INTEGRATION_ACTION_VERBS.join(', ')}`);
|
|
2303
|
+
}
|
|
2304
|
+
const single = await generator.GenerateAction(integrationName, objectName, verb, u, p);
|
|
2305
|
+
return [single];
|
|
2306
|
+
}
|
|
2307
|
+
return generator.GenerateActionsForObject(integrationName, objectName, u, p);
|
|
2308
|
+
}
|
|
2309
|
+
/** Maps engine GenerateIntegrationActionResult[] into the GraphQL output shape. */
|
|
2310
|
+
mapGenerateActionResults(results) {
|
|
2311
|
+
const mapped = results.map(r => ({
|
|
2312
|
+
Success: r.Success,
|
|
2313
|
+
ActionID: r.ActionID,
|
|
2314
|
+
ActionName: r.ActionName,
|
|
2315
|
+
AlreadyExisted: r.AlreadyExisted,
|
|
2316
|
+
Verb: r.Verb,
|
|
2317
|
+
ObjectName: r.ObjectName,
|
|
2318
|
+
Message: r.Message,
|
|
2319
|
+
}));
|
|
2320
|
+
const successCount = mapped.filter(r => r.Success).length;
|
|
2321
|
+
const overallSuccess = mapped.length > 0 && mapped.every(r => r.Success);
|
|
2322
|
+
return {
|
|
2323
|
+
Success: overallSuccess,
|
|
2324
|
+
Message: `Generated ${successCount}/${mapped.length} action(s)`,
|
|
2325
|
+
Results: mapped,
|
|
2326
|
+
};
|
|
2327
|
+
}
|
|
1774
2328
|
/**
|
|
1775
2329
|
* Returns the connector's default configuration for quick setup.
|
|
1776
2330
|
* Not all connectors provide defaults — returns Success: false if unavailable.
|
|
@@ -2301,6 +2855,56 @@ let IntegrationDiscoveryResolver = class IntegrationDiscoveryResolver extends Re
|
|
|
2301
2855
|
throw new Error('System user not available');
|
|
2302
2856
|
return sysUser;
|
|
2303
2857
|
}
|
|
2858
|
+
/**
|
|
2859
|
+
* Per-company read-authorization check for run artifacts.
|
|
2860
|
+
*
|
|
2861
|
+
* Reuses MJ's established row-level-security pattern: a `RunView` on
|
|
2862
|
+
* `MJ: Company Integrations` executed under the *calling user's* context.
|
|
2863
|
+
* The data provider applies the same read-permission / RLS filtering it
|
|
2864
|
+
* applies to every other user-context read, so the record only comes back
|
|
2865
|
+
* when the caller is genuinely authorized to see that CompanyIntegration.
|
|
2866
|
+
* No row returned ⇒ either the ID doesn't exist or the caller has no rights
|
|
2867
|
+
* to it — in both cases we treat the caller as unauthorized.
|
|
2868
|
+
*
|
|
2869
|
+
* Results are memoized per call via the supplied `cache` map so a list of
|
|
2870
|
+
* many runs sharing a CompanyIntegrationID incurs at most one lookup each.
|
|
2871
|
+
*/
|
|
2872
|
+
async userCanReadCompanyIntegration(companyIntegrationID, user, cache) {
|
|
2873
|
+
const cached = cache.get(companyIntegrationID);
|
|
2874
|
+
if (cached !== undefined) {
|
|
2875
|
+
return cached;
|
|
2876
|
+
}
|
|
2877
|
+
const rv = new RunView();
|
|
2878
|
+
const result = await rv.RunView({
|
|
2879
|
+
EntityName: 'MJ: Company Integrations',
|
|
2880
|
+
ExtraFilter: `ID='${companyIntegrationID}'`,
|
|
2881
|
+
MaxRows: 1,
|
|
2882
|
+
ResultType: 'simple',
|
|
2883
|
+
Fields: ['ID']
|
|
2884
|
+
}, user);
|
|
2885
|
+
const authorized = result.Success && result.Results.length > 0;
|
|
2886
|
+
cache.set(companyIntegrationID, authorized);
|
|
2887
|
+
return authorized;
|
|
2888
|
+
}
|
|
2889
|
+
/**
|
|
2890
|
+
* Authorizes the caller for a single run artifact based on the run's
|
|
2891
|
+
* manifest CompanyIntegrationID. Returns true when the run is tenant-scoped
|
|
2892
|
+
* and the caller is authorized for that CompanyIntegration. Returns false
|
|
2893
|
+
* for tenant-scoped runs the caller may not read, OR for runs with no
|
|
2894
|
+
* CompanyIntegrationID (non-tenant-scoped artifacts are not exposed through
|
|
2895
|
+
* these per-company endpoints).
|
|
2896
|
+
*/
|
|
2897
|
+
async userCanReadRunArtifact(snap, user, cache) {
|
|
2898
|
+
const ciID = snap.manifest.companyIntegrationID;
|
|
2899
|
+
if (!ciID) {
|
|
2900
|
+
return false;
|
|
2901
|
+
}
|
|
2902
|
+
return this.userCanReadCompanyIntegration(ciID, user, cache);
|
|
2903
|
+
}
|
|
2904
|
+
/** Standard authorization-failure message for run-artifact endpoints. */
|
|
2905
|
+
notAuthorizedForCompanyIntegrationMessage(companyIntegrationID) {
|
|
2906
|
+
return `Not authorized to access runs for CompanyIntegration '${companyIntegrationID}'`;
|
|
2907
|
+
}
|
|
2304
2908
|
/**
|
|
2305
2909
|
* Loads the CompanyIntegration + its parent Integration, then resolves the
|
|
2306
2910
|
* appropriate connector via ConnectorFactory.
|
|
@@ -2356,6 +2960,126 @@ let IntegrationDiscoveryResolver = class IntegrationDiscoveryResolver extends Re
|
|
|
2356
2960
|
const testFn = connector.TestConnection.bind(connector);
|
|
2357
2961
|
return testFn(companyIntegration, user);
|
|
2358
2962
|
}
|
|
2963
|
+
/**
|
|
2964
|
+
* Runs IntegrationConnectorCreationPipeline + refreshes the metadata cache.
|
|
2965
|
+
* Shared by IntegrationCreateConnection's auto-refresh path and the
|
|
2966
|
+
* standalone IntegrationRefreshConnectorSchema mutation.
|
|
2967
|
+
*/
|
|
2968
|
+
/**
|
|
2969
|
+
* Reconstructs a `SourceSchemaInfo` from already-persisted IntegrationObject
|
|
2970
|
+
* and IntegrationObjectField rows held by `IntegrationEngineBase`'s
|
|
2971
|
+
* in-memory cache.
|
|
2972
|
+
*
|
|
2973
|
+
* Why this exists: after the Phase 0 v5.39.x `MJCompanyIntegrationEntityServer`
|
|
2974
|
+
* hook fires the pipeline on `IsActive false→true`, the IO/IOF rows are
|
|
2975
|
+
* already fresh. The legacy `IntegrationApplyAllBatch` / `ApplyAll` /
|
|
2976
|
+
* `ApplySchema` resolvers then called `connector.IntrospectSchema()` AGAIN
|
|
2977
|
+
* to feed `SchemaBuilder`, which produced a second full vendor-API
|
|
2978
|
+
* roundtrip (HubSpot 130 objects × 60+ DiscoverFields probes, ~100s
|
|
2979
|
+
* wasted on the user's HubSpot run). Using the persisted rows skips
|
|
2980
|
+
* that round-trip entirely.
|
|
2981
|
+
*
|
|
2982
|
+
* Caveats:
|
|
2983
|
+
* - The persisted `Type` is the MJ canonical type (string / int / datetime / …),
|
|
2984
|
+
* NOT the original vendor source type. `TypeMapper` in SchemaBuilder
|
|
2985
|
+
* accepts canonical types so DDL generation still works; if a connector
|
|
2986
|
+
* has unusual nuances around its source-type strings, fall back to live
|
|
2987
|
+
* introspect (the caller can pass `forceLive: true`).
|
|
2988
|
+
* - PrimaryKeyFields are recomputed from the IOF rows where
|
|
2989
|
+
* `IsPrimaryKey=true`.
|
|
2990
|
+
* - Foreign-key relationships are reconstructed from
|
|
2991
|
+
* `RelatedIntegrationObjectID` lookups against the same cache.
|
|
2992
|
+
*/
|
|
2993
|
+
buildSourceSchemaFromPersistedRows(integrationID, requestedNames) {
|
|
2994
|
+
const engine = IntegrationEngineBase.Instance;
|
|
2995
|
+
const ios = engine.GetIntegrationObjectsByIntegrationID(integrationID);
|
|
2996
|
+
const filter = requestedNames && requestedNames.length > 0
|
|
2997
|
+
? new Set(requestedNames.map(n => n.toLowerCase()))
|
|
2998
|
+
: null;
|
|
2999
|
+
// Cache (id → name) for FK relationship reconstruction. Same-integration
|
|
3000
|
+
// only — cross-integration relationships are not modeled in the slot table.
|
|
3001
|
+
const ioByID = new Map();
|
|
3002
|
+
for (const io of ios)
|
|
3003
|
+
ioByID.set(io.ID, io.Name);
|
|
3004
|
+
const result = { Objects: [] };
|
|
3005
|
+
for (const io of ios) {
|
|
3006
|
+
if (filter && !filter.has(io.Name.toLowerCase()))
|
|
3007
|
+
continue;
|
|
3008
|
+
const iofs = engine.GetIntegrationObjectFields(io.ID);
|
|
3009
|
+
const fields = iofs.map(iof => {
|
|
3010
|
+
const targetIOName = iof.RelatedIntegrationObjectID
|
|
3011
|
+
? ioByID.get(iof.RelatedIntegrationObjectID) ?? null
|
|
3012
|
+
: null;
|
|
3013
|
+
return {
|
|
3014
|
+
Name: iof.Name,
|
|
3015
|
+
Label: iof.DisplayName ?? iof.Name,
|
|
3016
|
+
Description: iof.Description ?? undefined,
|
|
3017
|
+
SourceType: iof.Type ?? 'string',
|
|
3018
|
+
IsRequired: iof.IsRequired ?? false,
|
|
3019
|
+
AllowsNull: iof.AllowsNull ?? undefined,
|
|
3020
|
+
MaxLength: iof.Length ?? null,
|
|
3021
|
+
Precision: iof.Precision ?? null,
|
|
3022
|
+
Scale: iof.Scale ?? null,
|
|
3023
|
+
DefaultValue: iof.DefaultValue ?? null,
|
|
3024
|
+
IsPrimaryKey: iof.IsPrimaryKey ?? false,
|
|
3025
|
+
IsUniqueKey: iof.IsUniqueKey ?? false,
|
|
3026
|
+
IsReadOnly: iof.IsReadOnly ?? false,
|
|
3027
|
+
IsForeignKey: !!iof.RelatedIntegrationObjectID,
|
|
3028
|
+
ForeignKeyTarget: targetIOName,
|
|
3029
|
+
};
|
|
3030
|
+
});
|
|
3031
|
+
result.Objects.push({
|
|
3032
|
+
ExternalName: io.Name,
|
|
3033
|
+
ExternalLabel: io.DisplayName ?? io.Name,
|
|
3034
|
+
Description: io.Description ?? undefined,
|
|
3035
|
+
Fields: fields,
|
|
3036
|
+
PrimaryKeyFields: fields.filter(f => f.IsPrimaryKey).map(f => f.Name),
|
|
3037
|
+
Relationships: fields
|
|
3038
|
+
.filter(f => f.IsForeignKey && f.ForeignKeyTarget)
|
|
3039
|
+
.map(f => ({
|
|
3040
|
+
FieldName: f.Name,
|
|
3041
|
+
TargetObject: f.ForeignKeyTarget,
|
|
3042
|
+
TargetField: 'ID',
|
|
3043
|
+
})),
|
|
3044
|
+
IncrementalWatermarkField: io.IncrementalWatermarkField ?? undefined,
|
|
3045
|
+
});
|
|
3046
|
+
}
|
|
3047
|
+
return result;
|
|
3048
|
+
}
|
|
3049
|
+
async runSchemaRefreshPipeline(companyIntegrationID, user, provider, universalPKConvention) {
|
|
3050
|
+
const { connector, companyIntegration } = await this.resolveConnector(companyIntegrationID, user, provider);
|
|
3051
|
+
const pipeline = new IntegrationConnectorCreationPipeline();
|
|
3052
|
+
const runOpts = {
|
|
3053
|
+
Connector: connector,
|
|
3054
|
+
CompanyIntegration: companyIntegration,
|
|
3055
|
+
ContextUser: user,
|
|
3056
|
+
Provider: provider,
|
|
3057
|
+
UniversalPKConvention: universalPKConvention || undefined,
|
|
3058
|
+
ConsoleMirror: true,
|
|
3059
|
+
TriggerType: 'Manual',
|
|
3060
|
+
};
|
|
3061
|
+
const result = await pipeline.Run(runOpts);
|
|
3062
|
+
// Refresh in-memory caches so downstream queries (object picker,
|
|
3063
|
+
// ApplyAll, etc.) see the just-written IO/IOF rows.
|
|
3064
|
+
await (provider ?? new Metadata()).Refresh();
|
|
3065
|
+
await IntegrationEngine.Instance.Config(true, user, provider);
|
|
3066
|
+
return {
|
|
3067
|
+
RunID: result.RunID,
|
|
3068
|
+
ObjectsCreated: result.PersistResult?.ObjectsCreated ?? 0,
|
|
3069
|
+
ObjectsUpdated: result.PersistResult?.ObjectsUpdated ?? 0,
|
|
3070
|
+
FieldsCreated: result.PersistResult?.FieldsCreated ?? 0,
|
|
3071
|
+
FieldsUpdated: result.PersistResult?.FieldsUpdated ?? 0,
|
|
3072
|
+
UnresolvedObjects: result.UnresolvedObjects,
|
|
3073
|
+
PKVerdicts: result.PKVerdicts.map(v => ({
|
|
3074
|
+
ObjectName: v.ObjectName,
|
|
3075
|
+
Confident: v.Confident,
|
|
3076
|
+
Nominee: v.Nominee,
|
|
3077
|
+
Confidence: v.Confidence,
|
|
3078
|
+
Strategy: v.Strategy,
|
|
3079
|
+
Reason: v.Reason,
|
|
3080
|
+
})),
|
|
3081
|
+
};
|
|
3082
|
+
}
|
|
2359
3083
|
/**
|
|
2360
3084
|
* Rolls back a freshly created connection by deleting both the CompanyIntegration and Credential records.
|
|
2361
3085
|
*/
|
|
@@ -2373,6 +3097,40 @@ let IntegrationDiscoveryResolver = class IntegrationDiscoveryResolver extends Re
|
|
|
2373
3097
|
LogError(`Rollback: failed to delete Credential: ${this.formatError(e)}`);
|
|
2374
3098
|
}
|
|
2375
3099
|
}
|
|
3100
|
+
/**
|
|
3101
|
+
* Cascades the deletion of a CompanyIntegration's linked Credential as part of the
|
|
3102
|
+
* supplied TransactionGroup, so the encrypted-credential row is removed atomically
|
|
3103
|
+
* with the rest of the connection cascade (rolls back together on tg failure).
|
|
3104
|
+
*
|
|
3105
|
+
* Safety:
|
|
3106
|
+
* - No-op when {@link credentialID} is null/empty.
|
|
3107
|
+
* - Skips the delete when ANOTHER CompanyIntegration still references the same
|
|
3108
|
+
* CredentialID (shared credential) — only the sole referencer may delete it.
|
|
3109
|
+
*
|
|
3110
|
+
* @returns true if the credential was attached to the tg for deletion; false otherwise.
|
|
3111
|
+
*/
|
|
3112
|
+
async cascadeDeleteCredential(credentialID, companyIntegrationID, tg, rv, provider, user) {
|
|
3113
|
+
if (!credentialID)
|
|
3114
|
+
return false; // no linked credential — nothing to do
|
|
3115
|
+
// Shared-credential safety: do not delete if any OTHER CompanyIntegration uses it.
|
|
3116
|
+
const sharedResult = await rv.RunView({
|
|
3117
|
+
EntityName: 'MJ: Company Integrations',
|
|
3118
|
+
ExtraFilter: `CredentialID='${credentialID}' AND ID<>'${companyIntegrationID}'`,
|
|
3119
|
+
ResultType: 'simple',
|
|
3120
|
+
Fields: ['ID']
|
|
3121
|
+
}, user);
|
|
3122
|
+
if (sharedResult.Success && sharedResult.Results.length > 0) {
|
|
3123
|
+
LogStatus(`IntegrationDeleteConnection: credential ${credentialID} is shared by ${sharedResult.Results.length} other connection(s); leaving it in place`);
|
|
3124
|
+
return false;
|
|
3125
|
+
}
|
|
3126
|
+
const credential = await provider.GetEntityObject('MJ: Credentials', user);
|
|
3127
|
+
const loaded = await credential.InnerLoad(CompositeKey.FromID(credentialID));
|
|
3128
|
+
if (!loaded)
|
|
3129
|
+
return false; // already gone — treat as nothing to cascade
|
|
3130
|
+
credential.TransactionGroup = tg;
|
|
3131
|
+
await credential.Delete();
|
|
3132
|
+
return true;
|
|
3133
|
+
}
|
|
2376
3134
|
/**
|
|
2377
3135
|
* Snapshots the current credential Values for a given credential ID so they can be restored on rollback.
|
|
2378
3136
|
*/
|
|
@@ -2472,7 +3230,7 @@ let IntegrationDiscoveryResolver = class IntegrationDiscoveryResolver extends Re
|
|
|
2472
3230
|
/**
|
|
2473
3231
|
* Creates a CompanyIntegration with a linked Credential entity for encrypted credential storage.
|
|
2474
3232
|
*/
|
|
2475
|
-
async IntegrationCreateConnection(input, testConnection, ctx) {
|
|
3233
|
+
async IntegrationCreateConnection(input, testConnection, runSchemaRefresh, universalPKConvention, ctx) {
|
|
2476
3234
|
try {
|
|
2477
3235
|
const user = this.getAuthenticatedUser(ctx);
|
|
2478
3236
|
const md = GetReadWriteProvider(ctx.providers, { allowFallbackToReadOnly: true });
|
|
@@ -2507,6 +3265,8 @@ let IntegrationDiscoveryResolver = class IntegrationDiscoveryResolver extends Re
|
|
|
2507
3265
|
return { Success: false, Message: `Failed to save CompanyIntegration: ${validationErrors}` };
|
|
2508
3266
|
}
|
|
2509
3267
|
// 3. Optionally test the connection; rollback on failure
|
|
3268
|
+
let testPassed = !testConnection; // if no test asked, treat as "passed" so the refresh below still runs
|
|
3269
|
+
let testMessage = '';
|
|
2510
3270
|
if (testConnection) {
|
|
2511
3271
|
const testResult = await this.testConnectionForCI(ci.ID, user, md);
|
|
2512
3272
|
if (!testResult.Success) {
|
|
@@ -2518,13 +3278,36 @@ let IntegrationDiscoveryResolver = class IntegrationDiscoveryResolver extends Re
|
|
|
2518
3278
|
ConnectionTestMessage: testResult.Message
|
|
2519
3279
|
};
|
|
2520
3280
|
}
|
|
3281
|
+
testPassed = true;
|
|
3282
|
+
testMessage = testResult.Message;
|
|
3283
|
+
}
|
|
3284
|
+
// 4. Auto-run schema refresh pipeline (intermittent server-side period).
|
|
3285
|
+
// Fires whenever runSchemaRefresh=true, regardless of whether the
|
|
3286
|
+
// caller also asked for a test. The wizard may have tested separately
|
|
3287
|
+
// and just be hitting Create to save.
|
|
3288
|
+
let schemaRefreshSummary;
|
|
3289
|
+
if (runSchemaRefresh) {
|
|
3290
|
+
try {
|
|
3291
|
+
const refreshResult = await this.runSchemaRefreshPipeline(ci.ID, user, md, universalPKConvention);
|
|
3292
|
+
schemaRefreshSummary = refreshResult;
|
|
3293
|
+
}
|
|
3294
|
+
catch (refreshErr) {
|
|
3295
|
+
// Refresh failure does NOT roll back the connection —
|
|
3296
|
+
// user can re-run via IntegrationRefreshConnectorSchema.
|
|
3297
|
+
LogError(`IntegrationCreateConnection: pipeline error — ${refreshErr}`);
|
|
3298
|
+
}
|
|
3299
|
+
}
|
|
3300
|
+
if (testConnection || schemaRefreshSummary) {
|
|
2521
3301
|
return {
|
|
2522
3302
|
Success: true,
|
|
2523
|
-
Message:
|
|
3303
|
+
Message: schemaRefreshSummary
|
|
3304
|
+
? `Connection created${testConnection ? ', test passed' : ''}, schema refresh: ${schemaRefreshSummary.ObjectsCreated} created, ${schemaRefreshSummary.ObjectsUpdated} updated, ${schemaRefreshSummary.UnresolvedObjects.length} PK-unresolved`
|
|
3305
|
+
: 'Connection created and test passed',
|
|
2524
3306
|
CompanyIntegrationID: ci.ID,
|
|
2525
3307
|
CredentialID: credentialID,
|
|
2526
|
-
ConnectionTestSuccess:
|
|
2527
|
-
ConnectionTestMessage:
|
|
3308
|
+
ConnectionTestSuccess: testPassed,
|
|
3309
|
+
ConnectionTestMessage: testMessage,
|
|
3310
|
+
SchemaRefresh: schemaRefreshSummary,
|
|
2528
3311
|
};
|
|
2529
3312
|
}
|
|
2530
3313
|
return {
|
|
@@ -2542,7 +3325,7 @@ let IntegrationDiscoveryResolver = class IntegrationDiscoveryResolver extends Re
|
|
|
2542
3325
|
/**
|
|
2543
3326
|
* Updates credential values and/or configuration on an existing CompanyIntegration.
|
|
2544
3327
|
*/
|
|
2545
|
-
async IntegrationUpdateConnection(companyIntegrationID, credentialValues, configuration, externalSystemID, testConnection, ctx) {
|
|
3328
|
+
async IntegrationUpdateConnection(companyIntegrationID, credentialValues, configuration, externalSystemID, testConnection, runSchemaRefresh, universalPKConvention, ctx) {
|
|
2546
3329
|
try {
|
|
2547
3330
|
const user = this.getAuthenticatedUser(ctx);
|
|
2548
3331
|
const md = GetReadWriteProvider(ctx.providers, { allowFallbackToReadOnly: true });
|
|
@@ -2586,7 +3369,23 @@ let IntegrationDiscoveryResolver = class IntegrationDiscoveryResolver extends Re
|
|
|
2586
3369
|
await this.revertUpdateConnection(ci, oldConfiguration, oldExternalSystemID, oldCredentialValues, user, md);
|
|
2587
3370
|
return { Success: false, Message: `Connection test failed: ${testResult.Message}. Changes have been reverted.` };
|
|
2588
3371
|
}
|
|
2589
|
-
|
|
3372
|
+
}
|
|
3373
|
+
// Auto-run schema refresh pipeline (intermittent server-side period).
|
|
3374
|
+
// Fires whenever runSchemaRefresh=true, regardless of whether the
|
|
3375
|
+
// caller also asked for a test — the wizard may have tested separately
|
|
3376
|
+
// already and is just hitting Update to save edits.
|
|
3377
|
+
if (runSchemaRefresh) {
|
|
3378
|
+
try {
|
|
3379
|
+
const refreshResult = await this.runSchemaRefreshPipeline(companyIntegrationID, user, md, universalPKConvention);
|
|
3380
|
+
return {
|
|
3381
|
+
Success: true,
|
|
3382
|
+
Message: `Updated, schema refresh: ${refreshResult.ObjectsCreated} created, ${refreshResult.ObjectsUpdated} updated, ${refreshResult.UnresolvedObjects.length} PK-unresolved`,
|
|
3383
|
+
};
|
|
3384
|
+
}
|
|
3385
|
+
catch (refreshErr) {
|
|
3386
|
+
LogError(`IntegrationUpdateConnection: pipeline error — ${refreshErr}`);
|
|
3387
|
+
return { Success: true, Message: `Updated (schema refresh failed: ${this.formatError(refreshErr)})` };
|
|
3388
|
+
}
|
|
2590
3389
|
}
|
|
2591
3390
|
return { Success: true, Message: 'Updated' };
|
|
2592
3391
|
}
|
|
@@ -2671,25 +3470,54 @@ let IntegrationDiscoveryResolver = class IntegrationDiscoveryResolver extends Re
|
|
|
2671
3470
|
if (!entityID) {
|
|
2672
3471
|
return { Success: false, Message: `No EntityID or EntityName for "${mapDef.ExternalObjectName}"`, Created: created };
|
|
2673
3472
|
}
|
|
2674
|
-
const em = await md.GetEntityObject('MJ: Company Integration Entity Maps', user);
|
|
2675
|
-
em.NewRecord();
|
|
2676
|
-
em.CompanyIntegrationID = companyIntegrationID;
|
|
2677
|
-
em.ExternalObjectName = mapDef.ExternalObjectName;
|
|
2678
|
-
em.EntityID = entityID;
|
|
2679
3473
|
const syncDir = mapDef.SyncDirection || 'Pull';
|
|
2680
3474
|
if (!isValidSyncDirection(syncDir)) {
|
|
2681
3475
|
return { Success: false, Message: `Invalid SyncDirection "${syncDir}" for "${mapDef.ExternalObjectName}". Must be one of: ${VALID_SYNC_DIRECTIONS.join(', ')}`, Created: created };
|
|
2682
3476
|
}
|
|
3477
|
+
// Create-or-reuse by (connection, external object) — same idempotency rule as ApplyAll's
|
|
3478
|
+
// createSingleEntityMap. A blind NewRecord() here duplicates maps on every re-apply.
|
|
3479
|
+
const escapedObjectName = mapDef.ExternalObjectName.replace(/'/g, "''");
|
|
3480
|
+
const existingMapResult = await new RunView().RunView({
|
|
3481
|
+
EntityName: 'MJ: Company Integration Entity Maps',
|
|
3482
|
+
ExtraFilter: `CompanyIntegrationID='${companyIntegrationID}' AND ExternalObjectName='${escapedObjectName}'`,
|
|
3483
|
+
OrderBy: '__mj_CreatedAt ASC',
|
|
3484
|
+
MaxRows: 1,
|
|
3485
|
+
ResultType: 'entity_object',
|
|
3486
|
+
BypassCache: true, // idempotency must read COMMITTED state, not a possibly-stale filtered cache
|
|
3487
|
+
}, user);
|
|
3488
|
+
let em;
|
|
3489
|
+
if (existingMapResult.Success && existingMapResult.Results.length > 0) {
|
|
3490
|
+
em = existingMapResult.Results[0];
|
|
3491
|
+
}
|
|
3492
|
+
else {
|
|
3493
|
+
em = await md.GetEntityObject('MJ: Company Integration Entity Maps', user);
|
|
3494
|
+
em.NewRecord();
|
|
3495
|
+
em.CompanyIntegrationID = companyIntegrationID;
|
|
3496
|
+
em.ExternalObjectName = mapDef.ExternalObjectName;
|
|
3497
|
+
}
|
|
3498
|
+
em.EntityID = entityID;
|
|
2683
3499
|
em.SyncDirection = syncDir;
|
|
2684
3500
|
em.Priority = mapDef.Priority || 0;
|
|
2685
3501
|
em.Status = 'Active';
|
|
3502
|
+
if (mapDef.Configuration != null)
|
|
3503
|
+
em.Configuration = mapDef.Configuration;
|
|
2686
3504
|
if (!await em.Save()) {
|
|
2687
3505
|
return { Success: false, Message: `Failed to create map for ${mapDef.ExternalObjectName}`, Created: created };
|
|
2688
3506
|
}
|
|
2689
3507
|
const entityMapID = em.ID;
|
|
2690
|
-
// Create field maps if provided
|
|
3508
|
+
// Create field maps if provided (skip ones already mapped for this entity map)
|
|
2691
3509
|
if (mapDef.FieldMaps) {
|
|
3510
|
+
const existingFieldMaps = await new RunView().RunView({
|
|
3511
|
+
EntityName: 'MJ: Company Integration Field Maps',
|
|
3512
|
+
ExtraFilter: `EntityMapID='${entityMapID}'`,
|
|
3513
|
+
Fields: ['SourceFieldName'],
|
|
3514
|
+
ResultType: 'simple',
|
|
3515
|
+
BypassCache: true, // read committed field-map state for the idempotency skip
|
|
3516
|
+
}, user);
|
|
3517
|
+
const alreadyMapped = new Set((existingFieldMaps.Results ?? []).map(r => (r.SourceFieldName ?? '').toLowerCase()));
|
|
2692
3518
|
for (const fmDef of mapDef.FieldMaps) {
|
|
3519
|
+
if (alreadyMapped.has((fmDef.SourceFieldName ?? '').toLowerCase()))
|
|
3520
|
+
continue;
|
|
2693
3521
|
const fm = await md.GetEntityObject('MJ: Company Integration Field Maps', user);
|
|
2694
3522
|
fm.NewRecord();
|
|
2695
3523
|
fm.EntityMapID = entityMapID;
|
|
@@ -2728,10 +3556,21 @@ let IntegrationDiscoveryResolver = class IntegrationDiscoveryResolver extends Re
|
|
|
2728
3556
|
const user = this.getAuthenticatedUser(ctx);
|
|
2729
3557
|
const provider = GetReadWriteProvider(ctx.providers, { allowFallbackToReadOnly: true });
|
|
2730
3558
|
const { connector, companyIntegration } = await this.resolveConnector(companyIntegrationID, user, provider);
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
3559
|
+
// Reconstruct source schema from the IO/IOF rows already persisted by
|
|
3560
|
+
// the Phase 0 v5.39.x Save hook. Fall back to live IntrospectSchema only
|
|
3561
|
+
// for direct-API callers bypassing the wizard (empty IO cache).
|
|
2734
3562
|
const requestedNames = new Set(objects.map(o => o.SourceObjectName));
|
|
3563
|
+
let sourceSchema = this.buildSourceSchemaFromPersistedRows(companyIntegration.IntegrationID, Array.from(requestedNames));
|
|
3564
|
+
if (sourceSchema.Objects.length === 0) {
|
|
3565
|
+
LogError(`[IntegrationApplySchema] Persisted IO cache empty for ${companyIntegration.Integration}; falling back to live introspect.`);
|
|
3566
|
+
const introspect = connector.IntrospectSchema.bind(connector);
|
|
3567
|
+
sourceSchema = await introspect(companyIntegration, user);
|
|
3568
|
+
}
|
|
3569
|
+
else {
|
|
3570
|
+
console.log(`[IntegrationApplySchema] Reusing ${sourceSchema.Objects.length} persisted IOs for ${companyIntegration.Integration} ` +
|
|
3571
|
+
`— skipped duplicate IntrospectSchema (Save-hook already discovered).`);
|
|
3572
|
+
}
|
|
3573
|
+
await this.resolveObjectInputs(objects, sourceSchema, user);
|
|
2735
3574
|
const filteredSchema = {
|
|
2736
3575
|
Objects: sourceSchema.Objects.filter(o => requestedNames.has(o.ExternalName))
|
|
2737
3576
|
};
|
|
@@ -2849,67 +3688,75 @@ let IntegrationDiscoveryResolver = class IntegrationDiscoveryResolver extends Re
|
|
|
2849
3688
|
// Step 1: Resolve connector and derive schema name
|
|
2850
3689
|
const { connector, companyIntegration } = await this.resolveConnector(input.CompanyIntegrationID, user, provider);
|
|
2851
3690
|
const schemaName = this.deriveSchemaName(companyIntegration.Integration);
|
|
2852
|
-
// Step 1b: Ensure IntegrationEngine cache is populated so
|
|
2853
|
-
//
|
|
3691
|
+
// Step 1b: Ensure IntegrationEngine cache is populated so the persisted
|
|
3692
|
+
// IO/IOF rows are available for reconstruction below.
|
|
2854
3693
|
await IntegrationEngine.Instance.Config(false, user);
|
|
2855
|
-
// Step 2:
|
|
2856
|
-
|
|
2857
|
-
//
|
|
2858
|
-
//
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
.GetIntegrationObjectsByIntegrationID(companyIntegration.IntegrationID);
|
|
2877
|
-
const customObjects = sourceSchema.Objects
|
|
2878
|
-
.filter(o => !engineObjects
|
|
2879
|
-
.some(ex => ex.Name.toLowerCase() === o.ExternalName.toLowerCase() && !ex.IsCustom))
|
|
2880
|
-
.map(o => ({
|
|
2881
|
-
Name: o.ExternalName,
|
|
2882
|
-
DisplayName: o.ExternalLabel || o.ExternalName,
|
|
2883
|
-
Description: o.Description,
|
|
2884
|
-
SupportsWrite: false,
|
|
2885
|
-
Fields: o.Fields.map(f => ({
|
|
2886
|
-
Name: f.Name,
|
|
2887
|
-
DisplayName: f.Label || f.Name,
|
|
2888
|
-
Description: f.Description || '',
|
|
2889
|
-
Type: f.SourceType || 'string',
|
|
2890
|
-
IsRequired: f.IsRequired,
|
|
2891
|
-
IsReadOnly: false,
|
|
2892
|
-
IsPrimaryKey: f.IsPrimaryKey,
|
|
2893
|
-
})),
|
|
2894
|
-
}));
|
|
2895
|
-
await IntegrationSchemaSync.GenerateActionsForCustomObjects({
|
|
2896
|
-
IntegrationName: companyIntegration.Integration,
|
|
2897
|
-
CustomObjects: customObjects,
|
|
2898
|
-
SupportsSearch: connector.SupportsSearch,
|
|
2899
|
-
SupportsListing: connector.SupportsListing,
|
|
2900
|
-
ContextUser: user,
|
|
2901
|
-
});
|
|
3694
|
+
// Step 2: Reconstruct SourceSchemaInfo from the persisted IO/IOF rows
|
|
3695
|
+
// (already freshened by the Phase 0 v5.39.x MJCompanyIntegrationEntityServer
|
|
3696
|
+
// Save hook on IsActive false→true). Avoids the duplicate vendor-API
|
|
3697
|
+
// introspect that used to fire here.
|
|
3698
|
+
let sourceSchema = this.buildSourceSchemaFromPersistedRows(companyIntegration.IntegrationID);
|
|
3699
|
+
if (sourceSchema.Objects.length === 0) {
|
|
3700
|
+
// Fallback: the engine cache is empty (Save hook didn't run, or this
|
|
3701
|
+
// is a direct-API caller bypassing the wizard). Do a one-time live
|
|
3702
|
+
// introspect + persist + action-generation so the apply still proceeds.
|
|
3703
|
+
LogError(`[IntegrationApplyAll] Persisted IO cache empty for ${companyIntegration.Integration}; falling back to live introspect.`);
|
|
3704
|
+
sourceSchema = await connector.IntrospectSchema.bind(connector)(companyIntegration, user);
|
|
3705
|
+
try {
|
|
3706
|
+
const persistResult = await IntegrationSchemaSync.PersistDiscoveredSchema({
|
|
3707
|
+
IntegrationID: companyIntegration.IntegrationID,
|
|
3708
|
+
SourceSchema: sourceSchema,
|
|
3709
|
+
ContextUser: user,
|
|
3710
|
+
});
|
|
3711
|
+
if (persistResult.ObjectsCreated > 0 || persistResult.FieldsCreated > 0) {
|
|
3712
|
+
console.log(`[IntegrationApplyAll] Fallback persist: ` +
|
|
3713
|
+
`${persistResult.ObjectsCreated} new objects, ${persistResult.FieldsCreated} new fields, ` +
|
|
3714
|
+
`${persistResult.ObjectsUpdated} updated objects, ${persistResult.FieldsUpdated} updated fields`);
|
|
2902
3715
|
}
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
3716
|
+
if (persistResult.ObjectsCreated > 0) {
|
|
3717
|
+
try {
|
|
3718
|
+
const engineObjects = IntegrationEngine.Instance
|
|
3719
|
+
.GetIntegrationObjectsByIntegrationID(companyIntegration.IntegrationID);
|
|
3720
|
+
const customObjects = sourceSchema.Objects
|
|
3721
|
+
.filter(o => !engineObjects
|
|
3722
|
+
.some(ex => ex.Name.toLowerCase() === o.ExternalName.toLowerCase() && !ex.IsCustom))
|
|
3723
|
+
.map(o => ({
|
|
3724
|
+
Name: o.ExternalName,
|
|
3725
|
+
DisplayName: o.ExternalLabel || o.ExternalName,
|
|
3726
|
+
Description: o.Description,
|
|
3727
|
+
SupportsWrite: false,
|
|
3728
|
+
Fields: o.Fields.map(f => ({
|
|
3729
|
+
Name: f.Name,
|
|
3730
|
+
DisplayName: f.Label || f.Name,
|
|
3731
|
+
Description: f.Description || '',
|
|
3732
|
+
Type: f.SourceType || 'string',
|
|
3733
|
+
IsRequired: f.IsRequired,
|
|
3734
|
+
IsReadOnly: false,
|
|
3735
|
+
IsPrimaryKey: f.IsPrimaryKey,
|
|
3736
|
+
})),
|
|
3737
|
+
}));
|
|
3738
|
+
await IntegrationSchemaSync.GenerateActionsForCustomObjects({
|
|
3739
|
+
IntegrationName: companyIntegration.Integration,
|
|
3740
|
+
CustomObjects: customObjects,
|
|
3741
|
+
SupportsSearch: connector.SupportsSearch,
|
|
3742
|
+
SupportsListing: connector.SupportsListing,
|
|
3743
|
+
ContextUser: user,
|
|
3744
|
+
});
|
|
3745
|
+
}
|
|
3746
|
+
catch (actionErr) {
|
|
3747
|
+
const msg = actionErr instanceof Error ? actionErr.message : String(actionErr);
|
|
3748
|
+
console.warn(`[IntegrationApplyAll] Action generation warning (non-fatal): ${msg}`);
|
|
3749
|
+
}
|
|
2906
3750
|
}
|
|
2907
3751
|
}
|
|
3752
|
+
catch (persistErr) {
|
|
3753
|
+
const msg = persistErr instanceof Error ? persistErr.message : String(persistErr);
|
|
3754
|
+
console.warn(`[IntegrationApplyAll] Schema persistence warning (non-fatal): ${msg}`);
|
|
3755
|
+
}
|
|
2908
3756
|
}
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
console.warn(`[IntegrationApplyAll] Schema persistence warning (non-fatal): ${msg}`);
|
|
3757
|
+
else {
|
|
3758
|
+
console.log(`[IntegrationApplyAll] Reusing ${sourceSchema.Objects.length} persisted IOs for ${companyIntegration.Integration} ` +
|
|
3759
|
+
`— skipped duplicate IntrospectSchema (Save-hook already discovered).`);
|
|
2913
3760
|
}
|
|
2914
3761
|
const resolved = await this.resolveSourceObjectsToNames(input.SourceObjects, sourceSchema, user);
|
|
2915
3762
|
const resolvedNames = resolved.names;
|
|
@@ -3073,11 +3920,30 @@ let IntegrationDiscoveryResolver = class IntegrationDiscoveryResolver extends Re
|
|
|
3073
3920
|
LogError(`IntegrationApplyAll: entity not found for ${schemaName}.${obj.TableName}`);
|
|
3074
3921
|
return null;
|
|
3075
3922
|
}
|
|
3076
|
-
// Create entity map
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3923
|
+
// Create-or-reuse the entity map. Idempotency is REQUIRED here: ApplyAll is re-run on
|
|
3924
|
+
// every wizard re-apply (and by the test harness on every pull). Blindly NewRecord()-ing
|
|
3925
|
+
// multiplies the maps in lockstep (N applies → N duplicate maps per object), which
|
|
3926
|
+
// silently corrupts the record-map 1:1 completeness gate and makes the forward sync
|
|
3927
|
+
// process each object N times. Reuse the existing (connection, external object) map instead.
|
|
3928
|
+
const escapedObjectName = obj.SourceObjectName.replace(/'/g, "''");
|
|
3929
|
+
const existingMapResult = await new RunView().RunView({
|
|
3930
|
+
EntityName: 'MJ: Company Integration Entity Maps',
|
|
3931
|
+
ExtraFilter: `CompanyIntegrationID='${companyIntegrationID}' AND ExternalObjectName='${escapedObjectName}'`,
|
|
3932
|
+
OrderBy: '__mj_CreatedAt ASC',
|
|
3933
|
+
MaxRows: 1,
|
|
3934
|
+
ResultType: 'entity_object',
|
|
3935
|
+
BypassCache: true, // idempotency must read COMMITTED state, not a possibly-stale filtered cache
|
|
3936
|
+
}, user);
|
|
3937
|
+
let em;
|
|
3938
|
+
if (existingMapResult.Success && existingMapResult.Results.length > 0) {
|
|
3939
|
+
em = existingMapResult.Results[0]; // reuse — keeps the map stable across re-applies
|
|
3940
|
+
}
|
|
3941
|
+
else {
|
|
3942
|
+
em = await md.GetEntityObject('MJ: Company Integration Entity Maps', user);
|
|
3943
|
+
em.NewRecord();
|
|
3944
|
+
em.CompanyIntegrationID = companyIntegrationID;
|
|
3945
|
+
em.ExternalObjectName = obj.SourceObjectName;
|
|
3946
|
+
}
|
|
3081
3947
|
em.EntityID = entityInfo.ID;
|
|
3082
3948
|
em.SyncDirection = isValidSyncDirection(defaultSyncDirection) ? defaultSyncDirection : 'Pull';
|
|
3083
3949
|
em.Priority = obj.SourceObjectName.startsWith('assoc_') ? 10 : 0;
|
|
@@ -3102,7 +3968,21 @@ let IntegrationDiscoveryResolver = class IntegrationDiscoveryResolver extends Re
|
|
|
3102
3968
|
try {
|
|
3103
3969
|
const discoverFields = connector.DiscoverFields.bind(connector);
|
|
3104
3970
|
const fields = await discoverFields(companyIntegration, sourceObjectName, user);
|
|
3971
|
+
// Idempotency (mirrors the entity-map reuse above): don't re-create field maps that
|
|
3972
|
+
// already exist for this entity map, or re-applies multiply them in lockstep.
|
|
3973
|
+
const existingFieldMaps = await new RunView().RunView({
|
|
3974
|
+
EntityName: 'MJ: Company Integration Field Maps',
|
|
3975
|
+
ExtraFilter: `EntityMapID='${entityMapID}'`,
|
|
3976
|
+
Fields: ['SourceFieldName'],
|
|
3977
|
+
ResultType: 'simple',
|
|
3978
|
+
BypassCache: true, // read committed field-map state for the idempotency skip
|
|
3979
|
+
}, user);
|
|
3980
|
+
const alreadyMapped = new Set((existingFieldMaps.Results ?? []).map(r => (r.SourceFieldName ?? '').toLowerCase()));
|
|
3105
3981
|
for (const field of fields) {
|
|
3982
|
+
if (alreadyMapped.has(field.Name.toLowerCase())) {
|
|
3983
|
+
fieldCount++; // already present — count it so the reported total stays stable
|
|
3984
|
+
continue;
|
|
3985
|
+
}
|
|
3106
3986
|
const fm = await md.GetEntityObject('MJ: Company Integration Field Maps', user);
|
|
3107
3987
|
fm.NewRecord();
|
|
3108
3988
|
fm.EntityMapID = entityMapID;
|
|
@@ -3162,20 +4042,34 @@ let IntegrationDiscoveryResolver = class IntegrationDiscoveryResolver extends Re
|
|
|
3162
4042
|
*/
|
|
3163
4043
|
async buildSchemaForConnector(companyIntegrationID, objects, platform, user, skipGitCommit, skipRestart, provider, prefetchedSourceSchema) {
|
|
3164
4044
|
const { connector, companyIntegration } = await this.resolveConnector(companyIntegrationID, user, provider);
|
|
3165
|
-
//
|
|
3166
|
-
//
|
|
3167
|
-
//
|
|
3168
|
-
//
|
|
3169
|
-
//
|
|
3170
|
-
//
|
|
3171
|
-
//
|
|
4045
|
+
// Source-schema resolution order:
|
|
4046
|
+
// 1. Use prefetched schema if the caller passed one (legacy ApplyAllBatch path).
|
|
4047
|
+
// 2. Reconstruct from persisted IO/IOF rows — the Phase 0 v5.39.x Save hook
|
|
4048
|
+
// already discovered + persisted everything when the wizard flipped
|
|
4049
|
+
// `IsActive false→true`. No need to re-hit the vendor API.
|
|
4050
|
+
// 3. Only when both above are empty do we fall back to live IntrospectSchema
|
|
4051
|
+
// (direct-API callers bypassing the wizard).
|
|
4052
|
+
//
|
|
4053
|
+
// Pre-Phase-0 the legacy path ran introspect TWICE per apply — once in the
|
|
4054
|
+
// resolver and once here — which doubled probe time on connectors like Sage
|
|
4055
|
+
// Intacct AND silently dropped selections when the second pass returned
|
|
4056
|
+
// fewer objects than the first (rate limits, transient errors).
|
|
3172
4057
|
let sourceSchema;
|
|
3173
4058
|
if (prefetchedSourceSchema) {
|
|
3174
4059
|
sourceSchema = prefetchedSourceSchema;
|
|
3175
4060
|
}
|
|
3176
4061
|
else {
|
|
3177
|
-
const
|
|
3178
|
-
sourceSchema =
|
|
4062
|
+
const requestedNamesForReuse = objects.map(o => o.SourceObjectName);
|
|
4063
|
+
sourceSchema = this.buildSourceSchemaFromPersistedRows(companyIntegration.IntegrationID, requestedNamesForReuse);
|
|
4064
|
+
if (sourceSchema.Objects.length === 0) {
|
|
4065
|
+
LogError(`[buildSchemaForConnector] Persisted IO cache empty for ${companyIntegration.Integration}; falling back to live introspect.`);
|
|
4066
|
+
const introspect = connector.IntrospectSchema.bind(connector);
|
|
4067
|
+
sourceSchema = await introspect(companyIntegration, user);
|
|
4068
|
+
}
|
|
4069
|
+
else {
|
|
4070
|
+
console.log(`[buildSchemaForConnector] Reusing ${sourceSchema.Objects.length} persisted IOs for ${companyIntegration.Integration} ` +
|
|
4071
|
+
`— skipped duplicate IntrospectSchema (Save-hook already discovered).`);
|
|
4072
|
+
}
|
|
3179
4073
|
}
|
|
3180
4074
|
// Normalize names to match source schema casing
|
|
3181
4075
|
const nameMap = new Map(sourceSchema.Objects.map(o => [o.ExternalName.toLowerCase(), o.ExternalName]));
|
|
@@ -3218,6 +4112,16 @@ let IntegrationDiscoveryResolver = class IntegrationDiscoveryResolver extends Re
|
|
|
3218
4112
|
try {
|
|
3219
4113
|
const user = this.getAuthenticatedUser(ctx);
|
|
3220
4114
|
await IntegrationEngine.Instance.Config(false, user);
|
|
4115
|
+
// Reject upfront if the connector is deactivated. The engine also gates this
|
|
4116
|
+
// authoritatively (no run record is created), but returning Success:false here gives the
|
|
4117
|
+
// client immediate, unambiguous feedback instead of an optimistic fire-and-forget that
|
|
4118
|
+
// silently no-ops. IsActive is boolean|null — only an explicit false rejects (null/unset
|
|
4119
|
+
// connections predating the flag are unaffected).
|
|
4120
|
+
const ciProvider = GetReadOnlyProvider(ctx.providers, { allowFallbackToReadWrite: true });
|
|
4121
|
+
const ciCheck = await ciProvider.GetEntityObject('MJ: Company Integrations', user);
|
|
4122
|
+
if (await ciCheck.InnerLoad(CompositeKey.FromID(companyIntegrationID)) && ciCheck.IsActive === false) {
|
|
4123
|
+
return { Success: false, Message: 'Connector is deactivated (IsActive=false); sync not started' };
|
|
4124
|
+
}
|
|
3221
4125
|
const syncOptions = {};
|
|
3222
4126
|
if (fullSync)
|
|
3223
4127
|
syncOptions.FullSync = true;
|
|
@@ -3313,9 +4217,12 @@ let IntegrationDiscoveryResolver = class IntegrationDiscoveryResolver extends Re
|
|
|
3313
4217
|
return { Success: false, Message: `Company Integration not found: ${companyIntegrationID}` };
|
|
3314
4218
|
}
|
|
3315
4219
|
const companyIntegration = ciResult.Results[0];
|
|
3316
|
-
// Load the Integration entity to get the ClassName for connector resolution
|
|
4220
|
+
// Load the Integration entity to get the ClassName for connector resolution.
|
|
4221
|
+
// Entity is registered as 'MJ: Integrations' (the "MJ: " prefix is required — the sibling
|
|
4222
|
+
// CompanyIntegration lookup above uses 'MJ: Company Integrations'); the bare 'Integrations'
|
|
4223
|
+
// name is not in metadata, which dead-lettered the entire IntegrationWriteRecord (push) path.
|
|
3317
4224
|
const integResult = await rv.RunView({
|
|
3318
|
-
EntityName: 'Integrations',
|
|
4225
|
+
EntityName: 'MJ: Integrations',
|
|
3319
4226
|
ExtraFilter: `ID='${companyIntegration.IntegrationID}'`,
|
|
3320
4227
|
MaxRows: 1,
|
|
3321
4228
|
ResultType: 'entity_object',
|
|
@@ -3585,7 +4492,8 @@ let IntegrationDiscoveryResolver = class IntegrationDiscoveryResolver extends Re
|
|
|
3585
4492
|
ExtraFilter: `CompanyIntegrationID='${companyIntegrationID}'`,
|
|
3586
4493
|
OrderBy: 'Priority ASC',
|
|
3587
4494
|
ResultType: 'simple',
|
|
3588
|
-
Fields: ['ID', 'EntityID', 'Entity', 'ExternalObjectName', 'SyncDirection', 'Priority', 'Status']
|
|
4495
|
+
Fields: ['ID', 'EntityID', 'Entity', 'ExternalObjectName', 'SyncDirection', 'Priority', 'Status', 'Configuration'],
|
|
4496
|
+
BypassCache: true, // operational list must reflect COMMITTED state (wizard/lifecycle act on it)
|
|
3589
4497
|
}, user);
|
|
3590
4498
|
if (!result.Success)
|
|
3591
4499
|
return { Success: false, Message: result.ErrorMessage || 'Query failed' };
|
|
@@ -3609,6 +4517,7 @@ let IntegrationDiscoveryResolver = class IntegrationDiscoveryResolver extends Re
|
|
|
3609
4517
|
ExtraFilter: `EntityMapID='${entityMapID}'`,
|
|
3610
4518
|
OrderBy: 'SourceFieldName',
|
|
3611
4519
|
ResultType: 'simple',
|
|
4520
|
+
BypassCache: true, // operational list must reflect COMMITTED state
|
|
3612
4521
|
Fields: ['ID', 'EntityMapID', 'SourceFieldName', 'DestinationFieldName', 'Status']
|
|
3613
4522
|
}, user);
|
|
3614
4523
|
if (!result.Success)
|
|
@@ -3645,6 +4554,8 @@ let IntegrationDiscoveryResolver = class IntegrationDiscoveryResolver extends Re
|
|
|
3645
4554
|
}
|
|
3646
4555
|
if (update.Priority != null)
|
|
3647
4556
|
em.Priority = update.Priority;
|
|
4557
|
+
if (update.Configuration != null)
|
|
4558
|
+
em.Configuration = update.Configuration;
|
|
3648
4559
|
if (update.Status != null) {
|
|
3649
4560
|
if (!isValidEntityMapStatus(update.Status)) {
|
|
3650
4561
|
errors.push(`${update.EntityMapID}: invalid Status "${update.Status}"`);
|
|
@@ -3653,7 +4564,7 @@ let IntegrationDiscoveryResolver = class IntegrationDiscoveryResolver extends Re
|
|
|
3653
4564
|
em.Status = update.Status;
|
|
3654
4565
|
}
|
|
3655
4566
|
if (!await em.Save())
|
|
3656
|
-
errors.push(`${update.EntityMapID}: failed to save`);
|
|
4567
|
+
errors.push(`${update.EntityMapID}: failed to save — ${em.LatestResult?.CompleteMessage ?? 'unknown error'}`);
|
|
3657
4568
|
}
|
|
3658
4569
|
if (errors.length > 0)
|
|
3659
4570
|
return { Success: false, Message: `Errors: ${errors.join('; ')}` };
|
|
@@ -3796,7 +4707,11 @@ let IntegrationDiscoveryResolver = class IntegrationDiscoveryResolver extends Re
|
|
|
3796
4707
|
EntityName: 'MJ: Company Integration Entity Maps',
|
|
3797
4708
|
ExtraFilter: `CompanyIntegrationID='${companyIntegrationID}'`,
|
|
3798
4709
|
ResultType: 'simple',
|
|
3799
|
-
Fields: ['ID', 'Status']
|
|
4710
|
+
Fields: ['ID', 'Status'],
|
|
4711
|
+
// Status must reflect the live state immediately after a deselect/reselect
|
|
4712
|
+
// (UpdateEntityMaps). Bypass the server RunView cache so ActiveEntityMaps never
|
|
4713
|
+
// reports a stale count right after a map's Status is toggled.
|
|
4714
|
+
BypassCache: true
|
|
3800
4715
|
},
|
|
3801
4716
|
{
|
|
3802
4717
|
EntityName: 'MJ: Company Integration Runs',
|
|
@@ -3804,7 +4719,8 @@ let IntegrationDiscoveryResolver = class IntegrationDiscoveryResolver extends Re
|
|
|
3804
4719
|
OrderBy: 'StartedAt DESC',
|
|
3805
4720
|
MaxRows: 1,
|
|
3806
4721
|
ResultType: 'simple',
|
|
3807
|
-
Fields: ['ID', 'Status', 'StartedAt', 'EndedAt', 'TotalRecords']
|
|
4722
|
+
Fields: ['ID', 'Status', 'StartedAt', 'EndedAt', 'TotalRecords'],
|
|
4723
|
+
BypassCache: true
|
|
3808
4724
|
}
|
|
3809
4725
|
], user);
|
|
3810
4726
|
const maps = mapsResult.Success ? mapsResult.Results : [];
|
|
@@ -3862,6 +4778,171 @@ let IntegrationDiscoveryResolver = class IntegrationDiscoveryResolver extends Re
|
|
|
3862
4778
|
return { Success: false, Message: this.formatError(e) };
|
|
3863
4779
|
}
|
|
3864
4780
|
}
|
|
4781
|
+
// ── STRUCTURED RUN ARTIFACTS (durable, growing JSONL streams) ────────
|
|
4782
|
+
/**
|
|
4783
|
+
* Lists integration runs (sync, connector-creation, RSU, discovery, …) from the
|
|
4784
|
+
* durable progress-artifact store, newest-first. Use this to render run history
|
|
4785
|
+
* with live status for a multi-tenant control surface. Optionally scope to a
|
|
4786
|
+
* single connector and/or run kind, or to only in-flight runs.
|
|
4787
|
+
*/
|
|
4788
|
+
async IntegrationListRuns(ctx, companyIntegrationID, runKind, inFlightOnly, limit) {
|
|
4789
|
+
try {
|
|
4790
|
+
const user = this.getAuthenticatedUser(ctx);
|
|
4791
|
+
// When a specific connector is requested, authorize it up front so an
|
|
4792
|
+
// unauthorized caller gets a clear denial rather than an empty list.
|
|
4793
|
+
const authCache = new Map();
|
|
4794
|
+
if (companyIntegrationID) {
|
|
4795
|
+
const authorized = await this.userCanReadCompanyIntegration(companyIntegrationID, user, authCache);
|
|
4796
|
+
if (!authorized) {
|
|
4797
|
+
return { Success: false, Message: this.notAuthorizedForCompanyIntegrationMessage(companyIntegrationID) };
|
|
4798
|
+
}
|
|
4799
|
+
}
|
|
4800
|
+
const reader = new IntegrationProgressReader();
|
|
4801
|
+
const snaps = await reader.ListRuns({
|
|
4802
|
+
companyIntegrationID,
|
|
4803
|
+
runKind: runKind,
|
|
4804
|
+
inFlightOnly: inFlightOnly ?? false,
|
|
4805
|
+
}, limit ?? 50);
|
|
4806
|
+
// Filter to only the runs the caller is authorized to read. When
|
|
4807
|
+
// scoped to a single (already-authorized) connector this is a no-op;
|
|
4808
|
+
// for the cross-connector listing it prevents one tenant from seeing
|
|
4809
|
+
// another tenant's runs.
|
|
4810
|
+
const authorizedSnaps = await this.filterAuthorizedRuns(snaps, user, authCache);
|
|
4811
|
+
return { Success: true, Message: `${authorizedSnaps.length} run(s)`, Runs: authorizedSnaps.map(s => this.toRunSummaryArtifact(s)) };
|
|
4812
|
+
}
|
|
4813
|
+
catch (e) {
|
|
4814
|
+
LogError(`IntegrationListRuns error: ${e}`);
|
|
4815
|
+
return { Success: false, Message: this.formatError(e) };
|
|
4816
|
+
}
|
|
4817
|
+
}
|
|
4818
|
+
/** Returns only the run snapshots the caller is authorized to read. */
|
|
4819
|
+
async filterAuthorizedRuns(snaps, user, authCache) {
|
|
4820
|
+
const authorized = [];
|
|
4821
|
+
for (const snap of snaps) {
|
|
4822
|
+
if (await this.userCanReadRunArtifact(snap, user, authCache)) {
|
|
4823
|
+
authorized.push(snap);
|
|
4824
|
+
}
|
|
4825
|
+
}
|
|
4826
|
+
return authorized;
|
|
4827
|
+
}
|
|
4828
|
+
/**
|
|
4829
|
+
* Returns the summary of a single run (manifest + terminal result + latest counts).
|
|
4830
|
+
* Pair with IntegrationTailRunEvents to read the full event stream.
|
|
4831
|
+
*/
|
|
4832
|
+
async IntegrationGetRun(runID, ctx) {
|
|
4833
|
+
try {
|
|
4834
|
+
const user = this.getAuthenticatedUser(ctx);
|
|
4835
|
+
const reader = new IntegrationProgressReader();
|
|
4836
|
+
const snap = await reader.GetRun(runID);
|
|
4837
|
+
if (!snap)
|
|
4838
|
+
return { Success: false, Message: `Run '${runID}' not found` };
|
|
4839
|
+
const authorized = await this.userCanReadRunArtifact(snap, user, new Map());
|
|
4840
|
+
if (!authorized) {
|
|
4841
|
+
const ciID = snap.manifest.companyIntegrationID;
|
|
4842
|
+
return {
|
|
4843
|
+
Success: false,
|
|
4844
|
+
Message: ciID
|
|
4845
|
+
? this.notAuthorizedForCompanyIntegrationMessage(ciID)
|
|
4846
|
+
: `Not authorized to access run '${runID}'`,
|
|
4847
|
+
};
|
|
4848
|
+
}
|
|
4849
|
+
return {
|
|
4850
|
+
Success: true,
|
|
4851
|
+
Message: 'OK',
|
|
4852
|
+
Run: this.toRunSummaryArtifact(snap),
|
|
4853
|
+
Errors: snap.result?.errors?.map(er => er.stage ? `[${er.stage}] ${er.message}` : er.message),
|
|
4854
|
+
};
|
|
4855
|
+
}
|
|
4856
|
+
catch (e) {
|
|
4857
|
+
LogError(`IntegrationGetRun error: ${e}`);
|
|
4858
|
+
return { Success: false, Message: this.formatError(e) };
|
|
4859
|
+
}
|
|
4860
|
+
}
|
|
4861
|
+
/**
|
|
4862
|
+
* Tails a run's structured event stream from a given sequence number. The stream
|
|
4863
|
+
* grows over the life of the run, so a client polls with the last LatestSeq it
|
|
4864
|
+
* saw to fetch only the new events. IsInFlight=false signals the run is terminal
|
|
4865
|
+
* and polling can stop.
|
|
4866
|
+
*/
|
|
4867
|
+
async IntegrationTailRunEvents(runID, ctx, sinceSeq) {
|
|
4868
|
+
try {
|
|
4869
|
+
const user = this.getAuthenticatedUser(ctx);
|
|
4870
|
+
const reader = new IntegrationProgressReader();
|
|
4871
|
+
const snap = await reader.GetRun(runID);
|
|
4872
|
+
if (!snap)
|
|
4873
|
+
return { Success: false, Message: `Run '${runID}' not found`, LatestSeq: sinceSeq ?? 0, IsInFlight: false };
|
|
4874
|
+
const authorized = await this.userCanReadRunArtifact(snap, user, new Map());
|
|
4875
|
+
if (!authorized) {
|
|
4876
|
+
const ciID = snap.manifest.companyIntegrationID;
|
|
4877
|
+
return {
|
|
4878
|
+
Success: false,
|
|
4879
|
+
Message: ciID
|
|
4880
|
+
? this.notAuthorizedForCompanyIntegrationMessage(ciID)
|
|
4881
|
+
: `Not authorized to access run '${runID}'`,
|
|
4882
|
+
LatestSeq: sinceSeq ?? 0,
|
|
4883
|
+
IsInFlight: false,
|
|
4884
|
+
};
|
|
4885
|
+
}
|
|
4886
|
+
const events = await reader.Tail(runID, sinceSeq ?? 0);
|
|
4887
|
+
const latestSeq = events.length > 0 ? events[events.length - 1].seq : (sinceSeq ?? 0);
|
|
4888
|
+
return {
|
|
4889
|
+
Success: true,
|
|
4890
|
+
Message: `${events.length} event(s)`,
|
|
4891
|
+
Events: events.map(ev => ({
|
|
4892
|
+
Ts: ev.ts,
|
|
4893
|
+
Seq: ev.seq,
|
|
4894
|
+
EventType: ev.eventType,
|
|
4895
|
+
Level: ev.level,
|
|
4896
|
+
Stage: ev.stage,
|
|
4897
|
+
Message: ev.message,
|
|
4898
|
+
Counts: this.toCountsOutput(ev.counts),
|
|
4899
|
+
DataJSON: ev.data ? JSON.stringify(ev.data) : undefined,
|
|
4900
|
+
ResumableStateJSON: ev.resumableState ? JSON.stringify(ev.resumableState) : undefined,
|
|
4901
|
+
})),
|
|
4902
|
+
LatestSeq: latestSeq,
|
|
4903
|
+
IsInFlight: snap.isInFlight,
|
|
4904
|
+
};
|
|
4905
|
+
}
|
|
4906
|
+
catch (e) {
|
|
4907
|
+
LogError(`IntegrationTailRunEvents error: ${e}`);
|
|
4908
|
+
return { Success: false, Message: this.formatError(e), LatestSeq: sinceSeq ?? 0, IsInFlight: false };
|
|
4909
|
+
}
|
|
4910
|
+
}
|
|
4911
|
+
/** Maps a reader snapshot to the GraphQL summary shape. */
|
|
4912
|
+
toRunSummaryArtifact(s) {
|
|
4913
|
+
return {
|
|
4914
|
+
RunID: s.manifest.runID,
|
|
4915
|
+
RunKind: s.manifest.runKind,
|
|
4916
|
+
IntegrationID: s.manifest.integrationID,
|
|
4917
|
+
CompanyIntegrationID: s.manifest.companyIntegrationID,
|
|
4918
|
+
ObjectName: s.manifest.objectName,
|
|
4919
|
+
TriggerType: s.manifest.triggerType,
|
|
4920
|
+
StartedAt: s.manifest.startedAt,
|
|
4921
|
+
IsInFlight: s.isInFlight,
|
|
4922
|
+
EventCount: s.eventCount,
|
|
4923
|
+
Success: s.result?.success,
|
|
4924
|
+
ExitReason: s.result?.exitReason,
|
|
4925
|
+
CompletedAt: s.result?.completedAt,
|
|
4926
|
+
DurationMs: s.result?.durationMs,
|
|
4927
|
+
LatestEventType: s.latestEvent?.eventType,
|
|
4928
|
+
LatestMessage: s.latestEvent?.message,
|
|
4929
|
+
Counts: this.toCountsOutput(s.counts),
|
|
4930
|
+
WarningCount: s.warningCount,
|
|
4931
|
+
Warnings: s.warnings?.map(w => `[${w.code}] ${w.stage}: ${w.message}`),
|
|
4932
|
+
};
|
|
4933
|
+
}
|
|
4934
|
+
/** Maps the reader's lowercase counts shape to the PascalCase GraphQL output. */
|
|
4935
|
+
toCountsOutput(c) {
|
|
4936
|
+
if (!c)
|
|
4937
|
+
return undefined;
|
|
4938
|
+
return {
|
|
4939
|
+
Processed: c.processed,
|
|
4940
|
+
Succeeded: c.succeeded,
|
|
4941
|
+
Failed: c.failed,
|
|
4942
|
+
Skipped: c.skipped,
|
|
4943
|
+
TotalKnown: c.totalKnown,
|
|
4944
|
+
};
|
|
4945
|
+
}
|
|
3865
4946
|
// ── CONNECTOR CAPABILITIES ──────────────────────────────────────────
|
|
3866
4947
|
/**
|
|
3867
4948
|
* Returns the CRUD capability flags for the connector bound to a CompanyIntegration.
|
|
@@ -3953,26 +5034,41 @@ let IntegrationDiscoveryResolver = class IntegrationDiscoveryResolver extends Re
|
|
|
3953
5034
|
}
|
|
3954
5035
|
}
|
|
3955
5036
|
else {
|
|
3956
|
-
// Legacy path (HubSpot, YourMembership, Sage Intacct, etc.)
|
|
3957
|
-
//
|
|
3958
|
-
//
|
|
3959
|
-
//
|
|
3960
|
-
//
|
|
3961
|
-
//
|
|
3962
|
-
//
|
|
3963
|
-
|
|
3964
|
-
|
|
3965
|
-
|
|
3966
|
-
|
|
3967
|
-
|
|
3968
|
-
|
|
3969
|
-
|
|
3970
|
-
|
|
3971
|
-
|
|
3972
|
-
|
|
5037
|
+
// Legacy path (HubSpot, YourMembership, Sage Intacct, etc.).
|
|
5038
|
+
//
|
|
5039
|
+
// Phase 0 v5.39.x change: the `MJCompanyIntegrationEntityServer.Save()`
|
|
5040
|
+
// hook already ran the full discovery + persist pipeline when
|
|
5041
|
+
// the wizard's Finish flipped `IsActive false→true`. The IO/IOF
|
|
5042
|
+
// rows in `IntegrationEngineBase`'s cache are already fresh.
|
|
5043
|
+
// Calling `connector.IntrospectSchema()` again here just to feed
|
|
5044
|
+
// `SchemaBuilder` doubles the vendor-API roundtrip — on HubSpot
|
|
5045
|
+
// (130 objects, 60+ DiscoverFields probes) that's an extra ~100s
|
|
5046
|
+
// wasted per Apply. Reconstruct `SourceSchemaInfo` from the
|
|
5047
|
+
// persisted rows instead.
|
|
5048
|
+
//
|
|
5049
|
+
// Persist is also skipped here — the Save hook already did it.
|
|
5050
|
+
sourceSchema = this.buildSourceSchemaFromPersistedRows(companyIntegration.IntegrationID);
|
|
5051
|
+
if (sourceSchema.Objects.length === 0) {
|
|
5052
|
+
// Defensive fallback: if the engine cache is empty (hook
|
|
5053
|
+
// didn't run, or this is a direct-API caller that bypasses
|
|
5054
|
+
// the wizard), do a one-time live introspect + persist so
|
|
5055
|
+
// the apply still proceeds. Rare.
|
|
5056
|
+
LogError(`[IntegrationApplyAllBatch] Persisted IO cache empty for ${companyIntegration.Integration}; falling back to live introspect.`);
|
|
5057
|
+
sourceSchema = await connector.IntrospectSchema.bind(connector)(companyIntegration, user);
|
|
5058
|
+
try {
|
|
5059
|
+
await IntegrationSchemaSync.PersistDiscoveredSchema({
|
|
5060
|
+
IntegrationID: companyIntegration.IntegrationID,
|
|
5061
|
+
SourceSchema: sourceSchema,
|
|
5062
|
+
ContextUser: user,
|
|
5063
|
+
});
|
|
5064
|
+
}
|
|
5065
|
+
catch (persistErr) {
|
|
5066
|
+
LogError(`IntegrationApplyAllBatch: PersistDiscoveredSchema fallback failed for ${companyIntegration.Integration}: ${persistErr}`);
|
|
5067
|
+
}
|
|
3973
5068
|
}
|
|
3974
|
-
|
|
3975
|
-
|
|
5069
|
+
else {
|
|
5070
|
+
console.log(`[IntegrationApplyAllBatch] Reusing ${sourceSchema.Objects.length} persisted IOs for ${companyIntegration.Integration} ` +
|
|
5071
|
+
`— skipped duplicate IntrospectSchema (Save-hook already discovered).`);
|
|
3976
5072
|
}
|
|
3977
5073
|
// Resolve names from BOTH ID lookups and direct names.
|
|
3978
5074
|
// Direct names skip the IntegrationObject DB roundtrip
|
|
@@ -4362,9 +5458,20 @@ let IntegrationDiscoveryResolver = class IntegrationDiscoveryResolver extends Re
|
|
|
4362
5458
|
await emp.Delete();
|
|
4363
5459
|
}
|
|
4364
5460
|
}
|
|
4365
|
-
// Step 7: Delete the CompanyIntegration itself
|
|
5461
|
+
// Step 7: Delete the CompanyIntegration itself FIRST, so its CredentialID FK reference
|
|
5462
|
+
// is gone before we delete the Credential. (Deleting the Credential first violates the
|
|
5463
|
+
// CompanyIntegration.CredentialID foreign key — the constraint is checked per-statement
|
|
5464
|
+
// inside the transaction — and rolls back the entire cascade.) Capture CredentialID
|
|
5465
|
+
// before Delete() since we still need it for Step 8.
|
|
5466
|
+
const linkedCredentialID = ci.CredentialID;
|
|
4366
5467
|
ci.TransactionGroup = tg;
|
|
4367
5468
|
await ci.Delete();
|
|
5469
|
+
// Step 8: Delete the linked Credential (encrypted-credential row) in the SAME
|
|
5470
|
+
// transaction group so it commits/rolls back atomically with the cascade — now safe
|
|
5471
|
+
// because the referencing CompanyIntegration delete is queued ahead of it. Skips
|
|
5472
|
+
// silently when there is no CredentialID, and refuses to delete a credential still
|
|
5473
|
+
// referenced by ANOTHER CompanyIntegration (shared credential).
|
|
5474
|
+
const credentialDeleted = await this.cascadeDeleteCredential(linkedCredentialID, companyIntegrationID, tg, rv, md, sysUser);
|
|
4368
5475
|
// Submit the transaction
|
|
4369
5476
|
const submitted = await tg.Submit();
|
|
4370
5477
|
if (!submitted) {
|
|
@@ -4379,6 +5486,7 @@ let IntegrationDiscoveryResolver = class IntegrationDiscoveryResolver extends Re
|
|
|
4379
5486
|
EntityMapsDeleted: entityMapsDeleted,
|
|
4380
5487
|
FieldMapsDeleted: fieldMapsDeleted,
|
|
4381
5488
|
SchedulesDeleted: schedulesDeleted,
|
|
5489
|
+
CredentialDeleted: credentialDeleted,
|
|
4382
5490
|
};
|
|
4383
5491
|
}
|
|
4384
5492
|
catch (e) {
|
|
@@ -4589,6 +5697,25 @@ __decorate([
|
|
|
4589
5697
|
__metadata("design:paramtypes", [String, Object]),
|
|
4590
5698
|
__metadata("design:returntype", Promise)
|
|
4591
5699
|
], IntegrationDiscoveryResolver.prototype, "IntegrationTestConnection", null);
|
|
5700
|
+
__decorate([
|
|
5701
|
+
Mutation(() => RefreshConnectorSchemaOutput),
|
|
5702
|
+
__param(0, Arg("companyIntegrationID")),
|
|
5703
|
+
__param(1, Arg("universalPKConvention", { nullable: true, description: "Optional vendor-wide PK convention hint (e.g. 'id' for HubSpot)" })),
|
|
5704
|
+
__param(2, Ctx()),
|
|
5705
|
+
__metadata("design:type", Function),
|
|
5706
|
+
__metadata("design:paramtypes", [String, String, Object]),
|
|
5707
|
+
__metadata("design:returntype", Promise)
|
|
5708
|
+
], IntegrationDiscoveryResolver.prototype, "IntegrationRefreshConnectorSchema", null);
|
|
5709
|
+
__decorate([
|
|
5710
|
+
Mutation(() => IntegrationGenerateActionOutput),
|
|
5711
|
+
__param(0, Arg("integrationName")),
|
|
5712
|
+
__param(1, Arg("objectName")),
|
|
5713
|
+
__param(2, Arg("verb", { nullable: true, description: "Optional CRUD verb (Get|Create|Update|Delete|Search|List). Omit to generate all applicable verbs for the object." })),
|
|
5714
|
+
__param(3, Ctx()),
|
|
5715
|
+
__metadata("design:type", Function),
|
|
5716
|
+
__metadata("design:paramtypes", [String, String, String, Object]),
|
|
5717
|
+
__metadata("design:returntype", Promise)
|
|
5718
|
+
], IntegrationDiscoveryResolver.prototype, "IntegrationGenerateAction", null);
|
|
4592
5719
|
__decorate([
|
|
4593
5720
|
Query(() => DefaultConfigOutput),
|
|
4594
5721
|
__param(0, Arg("companyIntegrationID")),
|
|
@@ -4630,9 +5757,11 @@ __decorate([
|
|
|
4630
5757
|
Mutation(() => CreateConnectionOutput),
|
|
4631
5758
|
__param(0, Arg("input")),
|
|
4632
5759
|
__param(1, Arg("testConnection", () => Boolean, { defaultValue: false })),
|
|
4633
|
-
__param(2,
|
|
5760
|
+
__param(2, Arg("runSchemaRefresh", () => Boolean, { defaultValue: true, description: "When true (default) and TestConnection succeeds, automatically runs IntegrationConnectorCreationPipeline (live introspect → persist Declared/Discovered/Custom → SoftPKClassifier). The intermittent server-side work the wizard's Forward step represents." })),
|
|
5761
|
+
__param(3, Arg("universalPKConvention", { nullable: true, description: "Optional vendor-wide PK hint (e.g. 'id' for HubSpot). Improves SoftPKClassifier convergence." })),
|
|
5762
|
+
__param(4, Ctx()),
|
|
4634
5763
|
__metadata("design:type", Function),
|
|
4635
|
-
__metadata("design:paramtypes", [CreateConnectionInput, Boolean, Object]),
|
|
5764
|
+
__metadata("design:paramtypes", [CreateConnectionInput, Boolean, Boolean, String, Object]),
|
|
4636
5765
|
__metadata("design:returntype", Promise)
|
|
4637
5766
|
], IntegrationDiscoveryResolver.prototype, "IntegrationCreateConnection", null);
|
|
4638
5767
|
__decorate([
|
|
@@ -4642,9 +5771,11 @@ __decorate([
|
|
|
4642
5771
|
__param(2, Arg("configuration", { nullable: true })),
|
|
4643
5772
|
__param(3, Arg("externalSystemID", { nullable: true })),
|
|
4644
5773
|
__param(4, Arg("testConnection", () => Boolean, { defaultValue: false })),
|
|
4645
|
-
__param(5,
|
|
5774
|
+
__param(5, Arg("runSchemaRefresh", () => Boolean, { defaultValue: true, description: "When true (default) and TestConnection succeeds, automatically runs IntegrationConnectorCreationPipeline. Same intermittent server-side step as the create flow." })),
|
|
5775
|
+
__param(6, Arg("universalPKConvention", { nullable: true, description: "Optional vendor-wide PK hint (e.g. 'id' for HubSpot)" })),
|
|
5776
|
+
__param(7, Ctx()),
|
|
4646
5777
|
__metadata("design:type", Function),
|
|
4647
|
-
__metadata("design:paramtypes", [String, String, String, String, Boolean, Object]),
|
|
5778
|
+
__metadata("design:paramtypes", [String, String, String, String, Boolean, Boolean, String, Object]),
|
|
4648
5779
|
__metadata("design:returntype", Promise)
|
|
4649
5780
|
], IntegrationDiscoveryResolver.prototype, "IntegrationUpdateConnection", null);
|
|
4650
5781
|
__decorate([
|
|
@@ -4847,6 +5978,34 @@ __decorate([
|
|
|
4847
5978
|
__metadata("design:paramtypes", [String, Number, Object]),
|
|
4848
5979
|
__metadata("design:returntype", Promise)
|
|
4849
5980
|
], IntegrationDiscoveryResolver.prototype, "IntegrationGetSyncHistory", null);
|
|
5981
|
+
__decorate([
|
|
5982
|
+
Query(() => IntegrationListRunsOutput),
|
|
5983
|
+
__param(0, Ctx()),
|
|
5984
|
+
__param(1, Arg("companyIntegrationID", { nullable: true })),
|
|
5985
|
+
__param(2, Arg("runKind", { nullable: true })),
|
|
5986
|
+
__param(3, Arg("inFlightOnly", { nullable: true })),
|
|
5987
|
+
__param(4, Arg("limit", { defaultValue: 50 })),
|
|
5988
|
+
__metadata("design:type", Function),
|
|
5989
|
+
__metadata("design:paramtypes", [Object, String, String, Boolean, Number]),
|
|
5990
|
+
__metadata("design:returntype", Promise)
|
|
5991
|
+
], IntegrationDiscoveryResolver.prototype, "IntegrationListRuns", null);
|
|
5992
|
+
__decorate([
|
|
5993
|
+
Query(() => IntegrationRunDetailOutput),
|
|
5994
|
+
__param(0, Arg("runID")),
|
|
5995
|
+
__param(1, Ctx()),
|
|
5996
|
+
__metadata("design:type", Function),
|
|
5997
|
+
__metadata("design:paramtypes", [String, Object]),
|
|
5998
|
+
__metadata("design:returntype", Promise)
|
|
5999
|
+
], IntegrationDiscoveryResolver.prototype, "IntegrationGetRun", null);
|
|
6000
|
+
__decorate([
|
|
6001
|
+
Query(() => IntegrationRunEventsOutput),
|
|
6002
|
+
__param(0, Arg("runID")),
|
|
6003
|
+
__param(1, Ctx()),
|
|
6004
|
+
__param(2, Arg("sinceSeq", { defaultValue: 0 })),
|
|
6005
|
+
__metadata("design:type", Function),
|
|
6006
|
+
__metadata("design:paramtypes", [String, Object, Number]),
|
|
6007
|
+
__metadata("design:returntype", Promise)
|
|
6008
|
+
], IntegrationDiscoveryResolver.prototype, "IntegrationTailRunEvents", null);
|
|
4850
6009
|
__decorate([
|
|
4851
6010
|
Query(() => ConnectorCapabilitiesOutput),
|
|
4852
6011
|
__param(0, Arg("companyIntegrationID")),
|